Skip to content

Commit 21a893c

Browse files
committed
fixed a bug where a token is not refreshed
1 parent d6340ea commit 21a893c

18 files changed

+449
-322
lines changed

camunda-sdk-java/java-common/src/main/java/io/camunda/common/auth/Authentication.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
public interface Authentication {
66

7-
Authentication build();
87

98
Map.Entry<String, String> getTokenHeader(Product product);
109

1110
void resetToken(Product product);
11+
12+
interface AuthenticationBuilder {
13+
Authentication build();
14+
}
1215
}

camunda-sdk-java/java-common/src/main/java/io/camunda/common/auth/DefaultNoopAuthentication.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ public class DefaultNoopAuthentication implements Authentication {
1717

1818
private final String errorMessage = "Unable to determine authentication. Please check your configuration";
1919

20-
@Override
21-
public Authentication build() {
20+
public DefaultNoopAuthentication() {
2221
LOG.error(errorMessage);
23-
return this;
2422
}
2523

2624
@Override
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,70 @@
11
package io.camunda.common.auth;
22

3-
/**
4-
* TODO: Figure out common functionality between SaaS and Self Managed that can be inserted here
5-
* to reduce code duplication. If not, remove this class
6-
*/
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.lang.invoke.MethodHandles;
7+
import java.time.LocalDateTime;
8+
import java.util.AbstractMap;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Map.Entry;
12+
13+
714
public abstract class JwtAuthentication implements Authentication {
15+
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles
16+
.lookup().lookupClass());
17+
18+
private final JwtConfig jwtConfig;
19+
private final Map<Product, JwtToken> tokens = new HashMap<>();
20+
21+
protected JwtAuthentication(JwtConfig jwtConfig) {this.jwtConfig = jwtConfig;}
22+
23+
public JwtConfig getJwtConfig() {
24+
return jwtConfig;
25+
}
26+
27+
@Override
28+
public final void resetToken(Product product) {
29+
tokens.remove(product);
30+
}
31+
@Override
32+
public final Entry<String, String> getTokenHeader(Product product) {
33+
if (!tokens.containsKey(product) || !isValid(tokens.get(product))) {
34+
JwtToken newToken = generateToken(product,jwtConfig.getProduct(product));
35+
tokens.put(product, newToken);
36+
}
37+
return authHeader(tokens.get(product).getToken());
38+
}
39+
40+
protected abstract JwtToken generateToken(Product product, JwtCredential credential);
41+
42+
private Entry<String,String> authHeader(String token){
43+
return new AbstractMap.SimpleEntry<>("Authorization", "Bearer " + token);
44+
}
45+
46+
private boolean isValid(JwtToken jwtToken) {
47+
// a token is only counted valid if it is only valid for at least 30 seconds
48+
return jwtToken.getExpiry().isAfter(LocalDateTime.now().minusSeconds(30));
49+
}
50+
51+
52+
53+
protected static class JwtToken {
54+
private final String token;
55+
private final LocalDateTime expiry;
56+
57+
public JwtToken(String token, LocalDateTime expiry) {
58+
this.token = token;
59+
this.expiry = expiry;
60+
}
61+
62+
public String getToken() {
63+
return token;
64+
}
865

66+
public LocalDateTime getExpiry() {
67+
return expiry;
68+
}
69+
}
970
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.camunda.common.auth;
2+
3+
import io.camunda.common.auth.Authentication.AuthenticationBuilder;
4+
5+
public abstract class JwtAuthenticationBuilder<
6+
T extends JwtAuthenticationBuilder<?>> implements AuthenticationBuilder {
7+
private JwtConfig jwtConfig;
8+
9+
public final T withJwtConfig(JwtConfig jwtConfig) {
10+
this.jwtConfig = jwtConfig;
11+
return self();
12+
}
13+
14+
@Override
15+
public final Authentication build() {
16+
return build(jwtConfig);
17+
}
18+
19+
protected abstract T self();
20+
21+
protected abstract Authentication build(JwtConfig jwtConfig);
22+
}

camunda-sdk-java/java-common/src/main/java/io/camunda/common/auth/SaaSAuthentication.java

+24-42
Original file line numberDiff line numberDiff line change
@@ -19,69 +19,51 @@
1919
public class SaaSAuthentication extends JwtAuthentication {
2020

2121
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
22-
private JwtConfig jwtConfig;
23-
private Map<Product, String> tokens;
2422

25-
// TODO: have a single object mapper to be used all throughout the SDK, i.e.bean injection
26-
private JsonMapper jsonMapper = new SdkObjectMapper();
23+
private final JsonMapper jsonMapper;
2724

28-
public SaaSAuthentication() {
29-
tokens = new HashMap<>();
25+
public SaaSAuthentication(JwtConfig jwtConfig, JsonMapper jsonMapper) {
26+
super(jwtConfig);
27+
this.jsonMapper = jsonMapper;
3028
}
3129

3230
public static SaaSAuthenticationBuilder builder() {
3331
return new SaaSAuthenticationBuilder();
3432
}
3533

36-
public JwtConfig getJwtConfig() {
37-
return jwtConfig;
38-
}
39-
40-
public void setJwtConfig(JwtConfig jwtConfig) {
41-
this.jwtConfig = jwtConfig;
42-
}
43-
44-
@Override
45-
public Authentication build() {
46-
return this;
47-
}
4834

49-
@Override
50-
public void resetToken(Product product) {
51-
tokens.remove(product);
52-
}
53-
54-
private String retrieveToken(Product product, JwtCredential jwtCredential) {
55-
try(CloseableHttpClient client = HttpClients.createDefault()){
56-
HttpPost request = buildRequest(jwtCredential);
57-
TokenResponse tokenResponse = client.execute(request, response ->
58-
jsonMapper.fromJson(EntityUtils.toString(response.getEntity()), TokenResponse.class)
59-
);
60-
tokens.put(product, tokenResponse.getAccessToken());
61-
} catch (Exception e) {
35+
private TokenResponse retrieveToken(Product product, JwtCredential jwtCredential) {
36+
try (CloseableHttpClient client = HttpClients.createDefault()) {
37+
HttpPost request = buildRequest(jwtCredential);
38+
return client.execute(
39+
request,
40+
response ->
41+
jsonMapper.fromJson(EntityUtils.toString(response.getEntity()), TokenResponse.class));
42+
} catch (Exception e) {
6243
LOG.error("Authenticating for " + product + " failed due to " + e);
6344
throw new RuntimeException("Unable to authenticate", e);
6445
}
65-
return tokens.get(product);
6646
}
6747

6848
private HttpPost buildRequest(JwtCredential jwtCredential) {
6949
HttpPost httpPost = new HttpPost(jwtCredential.getAuthUrl());
7050
httpPost.addHeader("Content-Type", "application/json");
71-
TokenRequest tokenRequest = new TokenRequest(jwtCredential.getAudience(), jwtCredential.getClientId(), jwtCredential.getClientSecret());
51+
TokenRequest tokenRequest =
52+
new TokenRequest(
53+
jwtCredential.getAudience(),
54+
jwtCredential.getClientId(),
55+
jwtCredential.getClientSecret());
7256
httpPost.setEntity(new StringEntity(jsonMapper.toJson(tokenRequest)));
7357
return httpPost;
7458
}
7559

60+
61+
7662
@Override
77-
public Map.Entry<String, String> getTokenHeader(Product product) {
78-
String token;
79-
if (tokens.containsKey(product)) {
80-
token = tokens.get(product);
81-
} else {
82-
JwtCredential jwtCredential = jwtConfig.getProduct(product);
83-
token = retrieveToken(product, jwtCredential);
84-
}
85-
return new AbstractMap.SimpleEntry<>("Authorization", "Bearer " + token);
63+
protected JwtToken generateToken(Product product, JwtCredential credential) {
64+
TokenResponse tokenResponse = retrieveToken(product, credential);
65+
return new JwtToken(
66+
tokenResponse.getAccessToken(),
67+
LocalDateTime.now().plusSeconds(tokenResponse.getExpiresIn()));
8668
}
8769
}
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package io.camunda.common.auth;
22

3-
public class SaaSAuthenticationBuilder {
3+
import io.camunda.common.json.JsonMapper;
44

5-
SaaSAuthentication saaSAuthentication;
5+
public class SaaSAuthenticationBuilder extends JwtAuthenticationBuilder<SaaSAuthenticationBuilder> {
6+
private JsonMapper jsonMapper;
67

7-
SaaSAuthenticationBuilder() {
8-
saaSAuthentication = new SaaSAuthentication();
8+
public SaaSAuthenticationBuilder withJsonMapper(JsonMapper jsonMapper) {
9+
this.jsonMapper = jsonMapper;
10+
return this;
911
}
1012

11-
public SaaSAuthenticationBuilder jwtConfig(JwtConfig jwtConfig) {
12-
saaSAuthentication.setJwtConfig(jwtConfig);
13+
@Override
14+
protected SaaSAuthenticationBuilder self() {
1315
return this;
1416
}
1517

16-
public Authentication build() {
17-
return saaSAuthentication.build();
18+
@Override
19+
protected SaaSAuthentication build(JwtConfig jwtConfig) {
20+
return new SaaSAuthentication(jwtConfig, jsonMapper);
1821
}
19-
2022
}

camunda-sdk-java/java-common/src/main/java/io/camunda/common/auth/SelfManagedAuthentication.java

+16-61
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.lang.invoke.MethodHandles;
1818
import java.net.URLEncoder;
1919
import java.nio.charset.StandardCharsets;
20+
import java.time.LocalDateTime;
2021
import java.util.AbstractMap;
2122
import java.util.HashMap;
2223
import java.util.Map;
@@ -26,65 +27,23 @@ public class SelfManagedAuthentication extends JwtAuthentication {
2627

2728
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
2829

29-
private String authUrl;
30+
private final String authUrl;
31+
private final JsonMapper jsonMapper;
3032

31-
// TODO: Check with Identity about upcoming IDPs to abstract this
32-
private String keycloakRealm = "camunda-platform";
33-
private String keycloakUrl;
34-
private String keycloakTokenUrl;
35-
private JwtConfig jwtConfig;
36-
private Map<Product, String> tokens;
37-
38-
// TODO: have a single object mapper to be used all throughout the SDK, i.e.bean injection
39-
private JsonMapper jsonMapper = new SdkObjectMapper();
40-
41-
public SelfManagedAuthentication() {
42-
tokens = new HashMap<>();
33+
public SelfManagedAuthentication(JwtConfig jwtConfig, String authUrl, JsonMapper jsonMapper) {
34+
super(jwtConfig);
35+
this.authUrl = authUrl;
36+
this.jsonMapper = jsonMapper;
4337
}
4438

4539
public static SelfManagedAuthenticationBuilder builder() {
4640
return new SelfManagedAuthenticationBuilder();
4741
}
4842

49-
public void setKeycloakRealm(String keycloakRealm) {
50-
this.keycloakRealm = keycloakRealm;
51-
}
52-
53-
public void setKeycloakUrl(String keycloakUrl) {
54-
this.keycloakUrl = keycloakUrl;
55-
}
56-
57-
public void setKeycloakTokenUrl(String keycloakTokenUrl) {
58-
this.keycloakTokenUrl = keycloakTokenUrl;
59-
}
60-
61-
public JwtConfig getJwtConfig() {
62-
return jwtConfig;
63-
}
64-
65-
public void setJwtConfig(JwtConfig jwtConfig) {
66-
this.jwtConfig = jwtConfig;
67-
}
68-
69-
@Override
70-
public Authentication build() {
71-
if (keycloakTokenUrl != null) {
72-
authUrl = keycloakTokenUrl;
73-
} else {
74-
authUrl = keycloakUrl+"/auth/realms/"+keycloakRealm+"/protocol/openid-connect/token";
75-
}
76-
return this;
77-
}
78-
79-
@Override
80-
public void resetToken(Product product) {
81-
tokens.remove(product);
82-
}
83-
84-
private String retrieveToken(Product product, JwtCredential jwtCredential) {
43+
private TokenResponse retrieveToken(Product product, JwtCredential jwtCredential) {
8544
try(CloseableHttpClient client = HttpClients.createDefault()) {
8645
HttpPost request = buildRequest(jwtCredential);
87-
TokenResponse tokenResponse =
46+
return
8847
client.execute(
8948
request,
9049
response -> {
@@ -99,12 +58,10 @@ private String retrieveToken(Product product, JwtCredential jwtCredential) {
9958
+ EntityUtils.toString(response.getEntity()));
10059
}
10160
});
102-
tokens.put(product, tokenResponse.getAccessToken());
10361
} catch (Exception e) {
10462
LOG.error("Authenticating for " + product + " failed due to " + e);
10563
throw new SdkException("Unable to authenticate", e);
10664
}
107-
return tokens.get(product);
10865
}
10966

11067
private HttpPost buildRequest(JwtCredential jwtCredential) {
@@ -132,15 +89,13 @@ private HttpPost buildRequest(JwtCredential jwtCredential) {
13289
return httpPost;
13390
}
13491

92+
93+
13594
@Override
136-
public Map.Entry<String, String> getTokenHeader(Product product) {
137-
String token;
138-
if (tokens.containsKey(product)) {
139-
token = tokens.get(product);
140-
} else {
141-
JwtCredential jwtCredential = jwtConfig.getProduct(product);
142-
token = retrieveToken(product, jwtCredential);
143-
}
144-
return new AbstractMap.SimpleEntry<>("Authorization", "Bearer " + token);
95+
protected JwtToken generateToken(Product product, JwtCredential credential) {
96+
TokenResponse tokenResponse = retrieveToken(product, credential);
97+
return new JwtToken(
98+
tokenResponse.getAccessToken(),
99+
LocalDateTime.now().plusSeconds(tokenResponse.getExpiresIn()));
145100
}
146101
}

0 commit comments

Comments
 (0)