REST template concurrency
This commit is contained in:
parent
444c05a76c
commit
1960d99504
@ -36,10 +36,10 @@ public class DefaultJwtSettingsValidator implements JwtSettingsValidator {
|
||||
if (StringUtils.isEmpty(jwtSettings.getTokenIssuer())) {
|
||||
throw new DataValidationException("JWT token issuer should be specified!");
|
||||
}
|
||||
if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(15)) {
|
||||
if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) < TimeUnit.MINUTES.toSeconds(15)) {
|
||||
throw new DataValidationException("JWT refresh token expiration time should be at least 15 minutes!");
|
||||
}
|
||||
if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(1)) {
|
||||
if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) < TimeUnit.MINUTES.toSeconds(1)) {
|
||||
throw new DataValidationException("JWT token expiration time should be at least 1 minute!");
|
||||
}
|
||||
if (jwtSettings.getTokenExpirationTime() >= jwtSettings.getRefreshTokenExpTime()) {
|
||||
|
||||
6
pom.xml
6
pom.xml
@ -67,6 +67,7 @@
|
||||
<jackson.version>2.13.4</jackson.version>
|
||||
<jackson-databind.version>2.13.4.2</jackson-databind.version>
|
||||
<fasterxml-classmate.version>1.3.4</fasterxml-classmate.version>
|
||||
<auth0-jwt.version>4.2.1</auth0-jwt.version>
|
||||
<json-schema-validator.version>2.2.6</json-schema-validator.version>
|
||||
<californium.version>3.0.0</californium.version>
|
||||
<leshan.version>2.0.0-M5</leshan.version>
|
||||
@ -1429,6 +1430,11 @@
|
||||
<artifactId>classmate</artifactId>
|
||||
<version>${fasterxml-classmate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${auth0-jwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.fge</groupId>
|
||||
<artifactId>json-schema-validator</artifactId>
|
||||
|
||||
@ -47,6 +47,10 @@
|
||||
<groupId>org.thingsboard.common</groupId>
|
||||
<artifactId>util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.rest.client;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
@ -28,9 +29,6 @@ import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.support.HttpRequestWrapper;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@ -161,7 +159,6 @@ import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@ -171,6 +168,7 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.thingsboard.server.common.data.StringUtils.isEmpty;
|
||||
@ -178,40 +176,50 @@ import static org.thingsboard.server.common.data.StringUtils.isEmpty;
|
||||
/**
|
||||
* @author Andrew Shvayka
|
||||
*/
|
||||
public class RestClient implements ClientHttpRequestInterceptor, Closeable {
|
||||
public class RestClient implements Closeable {
|
||||
private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
|
||||
protected final RestTemplate restTemplate;
|
||||
protected final String baseURL;
|
||||
private String token;
|
||||
private String refreshToken;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass());
|
||||
|
||||
private static final long AVG_REQUEST_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
|
||||
protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken=";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass());
|
||||
protected final RestTemplate restTemplate;
|
||||
protected final RestTemplate loginRestTemplate;
|
||||
protected final String baseURL;
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private String mainToken;
|
||||
private String refreshToken;
|
||||
private long mainTokenExpTs;
|
||||
private long refreshTokenExpTs;
|
||||
private long clientServerTimeDiff;
|
||||
|
||||
public RestClient(String baseURL) {
|
||||
this(new RestTemplate(), baseURL);
|
||||
}
|
||||
|
||||
public RestClient(RestTemplate restTemplate, String baseURL) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.loginRestTemplate = new RestTemplate(restTemplate.getRequestFactory());
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException {
|
||||
HttpRequest wrapper = new HttpRequestWrapper(request);
|
||||
wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token);
|
||||
ClientHttpResponse response = execution.execute(wrapper, bytes);
|
||||
if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
|
||||
synchronized (this) {
|
||||
restTemplate.getInterceptors().remove(this);
|
||||
refreshToken();
|
||||
wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token);
|
||||
return execution.execute(wrapper, bytes);
|
||||
this.restTemplate.getInterceptors().add((request, bytes, execution) -> {
|
||||
HttpRequest wrapper = new HttpRequestWrapper(request);
|
||||
long calculatedTs = System.currentTimeMillis() + clientServerTimeDiff + AVG_REQUEST_TIMEOUT;
|
||||
if (calculatedTs > mainTokenExpTs) {
|
||||
synchronized (RestClient.this) {
|
||||
if (calculatedTs > mainTokenExpTs) {
|
||||
if (calculatedTs < refreshTokenExpTs) {
|
||||
refreshToken();
|
||||
} else {
|
||||
doLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + mainToken);
|
||||
return execution.execute(wrapper, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
public RestTemplate getRestTemplate() {
|
||||
@ -219,7 +227,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
return mainToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
@ -229,22 +237,32 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
|
||||
public void refreshToken() {
|
||||
Map<String, String> refreshTokenRequest = new HashMap<>();
|
||||
refreshTokenRequest.put("refreshToken", refreshToken);
|
||||
ResponseEntity<JsonNode> tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/token", refreshTokenRequest, JsonNode.class);
|
||||
setTokenInfo(tokenInfo.getBody());
|
||||
long ts = System.currentTimeMillis();
|
||||
ResponseEntity<JsonNode> tokenInfo = loginRestTemplate.postForEntity(baseURL + "/api/auth/token", refreshTokenRequest, JsonNode.class);
|
||||
setTokenInfo(ts, tokenInfo.getBody());
|
||||
}
|
||||
|
||||
public void login(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
doLogin();
|
||||
}
|
||||
|
||||
private void doLogin() {
|
||||
long ts = System.currentTimeMillis();
|
||||
Map<String, String> loginRequest = new HashMap<>();
|
||||
loginRequest.put("username", username);
|
||||
loginRequest.put("password", password);
|
||||
ResponseEntity<JsonNode> tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/login", loginRequest, JsonNode.class);
|
||||
setTokenInfo(tokenInfo.getBody());
|
||||
ResponseEntity<JsonNode> tokenInfo = loginRestTemplate.postForEntity(baseURL + "/api/auth/login", loginRequest, JsonNode.class);
|
||||
setTokenInfo(ts, tokenInfo.getBody());
|
||||
}
|
||||
|
||||
private void setTokenInfo(JsonNode tokenInfo) {
|
||||
this.token = tokenInfo.get("token").asText();
|
||||
private synchronized void setTokenInfo(long ts, JsonNode tokenInfo) {
|
||||
this.mainToken = tokenInfo.get("token").asText();
|
||||
this.refreshToken = tokenInfo.get("refreshToken").asText();
|
||||
restTemplate.getInterceptors().add(this);
|
||||
this.mainTokenExpTs = JWT.decode(this.mainToken).getExpiresAtAsInstant().toEpochMilli();
|
||||
this.refreshTokenExpTs = JWT.decode(refreshToken).getExpiresAtAsInstant().toEpochMilli();
|
||||
this.clientServerTimeDiff = JWT.decode(this.mainToken).getIssuedAtAsInstant().toEpochMilli() - ts;
|
||||
}
|
||||
|
||||
public Optional<AdminSettings> getAdminSettings(String key) {
|
||||
@ -3553,9 +3571,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (service != null) {
|
||||
service.shutdown();
|
||||
}
|
||||
service.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user