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