|
|
|
@ -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,16 +176,24 @@ 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"; |
|
|
|
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 token; |
|
|
|
private String refreshToken; |
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper(); |
|
|
|
private ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass()); |
|
|
|
|
|
|
|
protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; |
|
|
|
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); |
|
|
|
@ -195,23 +201,25 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { |
|
|
|
|
|
|
|
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(); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|