Commit bc6efa5e1e339b07e8b20ac2b59d26eb2713cd4f

Authored by Viacheslav Klimov
Committed by GitHub
1 parent e30ec49d

[3.3] [PROD-685] Provide user's session expiration when his auth data is changed (#4201)

* Provide user's session expiration when his auth data is changed

* Provide mock TokenOutdatingService for dao tests

* Increase time gap when checking if token is outdated

* Add license header for TokenOutdatingTest

* Refactor tokens outdating functionality to events usage

* Reset tokens on front-end after changing password
Showing 20 changed files with 427 additions and 122 deletions
@@ -17,7 +17,7 @@ package org.thingsboard.server.config; @@ -17,7 +17,7 @@ package org.thingsboard.server.config;
17 17
18 import org.springframework.boot.context.properties.ConfigurationProperties; 18 import org.springframework.boot.context.properties.ConfigurationProperties;
19 import org.springframework.context.annotation.Configuration; 19 import org.springframework.context.annotation.Configuration;
20 -import org.thingsboard.server.service.security.model.token.JwtToken; 20 +import org.thingsboard.server.common.data.security.model.JwtToken;
21 21
22 @Configuration 22 @Configuration
23 @ConfigurationProperties(prefix = "security.jwt") 23 @ConfigurationProperties(prefix = "security.jwt")
@@ -18,8 +18,9 @@ package org.thingsboard.server.controller; @@ -18,8 +18,9 @@ package org.thingsboard.server.controller;
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 import com.fasterxml.jackson.databind.node.ObjectNode; 20 import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import lombok.RequiredArgsConstructor;
21 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
22 -import org.springframework.beans.factory.annotation.Autowired; 23 +import org.springframework.context.ApplicationEventPublisher;
23 import org.springframework.http.HttpHeaders; 24 import org.springframework.http.HttpHeaders;
24 import org.springframework.http.HttpStatus; 25 import org.springframework.http.HttpStatus;
25 import org.springframework.http.ResponseEntity; 26 import org.springframework.http.ResponseEntity;
@@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RequestParam; @@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RequestParam;
32 import org.springframework.web.bind.annotation.ResponseBody; 33 import org.springframework.web.bind.annotation.ResponseBody;
33 import org.springframework.web.bind.annotation.ResponseStatus; 34 import org.springframework.web.bind.annotation.ResponseStatus;
34 import org.springframework.web.bind.annotation.RestController; 35 import org.springframework.web.bind.annotation.RestController;
  36 +import org.thingsboard.common.util.JacksonUtil;
35 import org.thingsboard.rule.engine.api.MailService; 37 import org.thingsboard.rule.engine.api.MailService;
36 import org.thingsboard.server.common.data.User; 38 import org.thingsboard.server.common.data.User;
37 import org.thingsboard.server.common.data.audit.ActionType; 39 import org.thingsboard.server.common.data.audit.ActionType;
@@ -39,48 +41,37 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -39,48 +41,37 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 import org.thingsboard.server.common.data.exception.ThingsboardException; 41 import org.thingsboard.server.common.data.exception.ThingsboardException;
40 import org.thingsboard.server.common.data.id.TenantId; 42 import org.thingsboard.server.common.data.id.TenantId;
41 import org.thingsboard.server.common.data.security.UserCredentials; 43 import org.thingsboard.server.common.data.security.UserCredentials;
  44 +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
  45 +import org.thingsboard.server.common.data.security.model.JwtToken;
  46 +import org.thingsboard.server.common.data.security.model.SecuritySettings;
  47 +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
42 import org.thingsboard.server.dao.audit.AuditLogService; 48 import org.thingsboard.server.dao.audit.AuditLogService;
43 import org.thingsboard.server.queue.util.TbCoreComponent; 49 import org.thingsboard.server.queue.util.TbCoreComponent;
44 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 50 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
45 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; 51 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
46 -import org.thingsboard.server.common.data.security.model.SecuritySettings;  
47 import org.thingsboard.server.service.security.model.SecurityUser; 52 import org.thingsboard.server.service.security.model.SecurityUser;
48 -import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;  
49 import org.thingsboard.server.service.security.model.UserPrincipal; 53 import org.thingsboard.server.service.security.model.UserPrincipal;
50 -import org.thingsboard.server.service.security.model.token.JwtToken;  
51 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 54 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
52 import org.thingsboard.server.service.security.system.SystemSecurityService; 55 import org.thingsboard.server.service.security.system.SystemSecurityService;
53 -import org.thingsboard.server.utils.MiscUtils;  
54 import ua_parser.Client; 56 import ua_parser.Client;
55 57
56 import javax.servlet.http.HttpServletRequest; 58 import javax.servlet.http.HttpServletRequest;
57 import java.net.URI; 59 import java.net.URI;
58 import java.net.URISyntaxException; 60 import java.net.URISyntaxException;
59 -import java.util.List;  
60 61
61 @RestController 62 @RestController
62 @TbCoreComponent 63 @TbCoreComponent
63 @RequestMapping("/api") 64 @RequestMapping("/api")
64 @Slf4j 65 @Slf4j
  66 +@RequiredArgsConstructor
65 public class AuthController extends BaseController { 67 public class AuthController extends BaseController {
66 -  
67 - @Autowired  
68 - private BCryptPasswordEncoder passwordEncoder;  
69 -  
70 - @Autowired  
71 - private JwtTokenFactory tokenFactory;  
72 -  
73 - @Autowired  
74 - private RefreshTokenRepository refreshTokenRepository;  
75 -  
76 - @Autowired  
77 - private MailService mailService;  
78 -  
79 - @Autowired  
80 - private SystemSecurityService systemSecurityService;  
81 -  
82 - @Autowired  
83 - private AuditLogService auditLogService; 68 + private final BCryptPasswordEncoder passwordEncoder;
  69 + private final JwtTokenFactory tokenFactory;
  70 + private final RefreshTokenRepository refreshTokenRepository;
  71 + private final MailService mailService;
  72 + private final SystemSecurityService systemSecurityService;
  73 + private final AuditLogService auditLogService;
  74 + private final ApplicationEventPublisher eventPublisher;
84 75
85 @PreAuthorize("isAuthenticated()") 76 @PreAuthorize("isAuthenticated()")
86 @RequestMapping(value = "/auth/user", method = RequestMethod.GET) 77 @RequestMapping(value = "/auth/user", method = RequestMethod.GET)
@@ -103,8 +94,7 @@ public class AuthController extends BaseController { @@ -103,8 +94,7 @@ public class AuthController extends BaseController {
103 @PreAuthorize("isAuthenticated()") 94 @PreAuthorize("isAuthenticated()")
104 @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) 95 @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
105 @ResponseStatus(value = HttpStatus.OK) 96 @ResponseStatus(value = HttpStatus.OK)
106 - public void changePassword (  
107 - @RequestBody JsonNode changePasswordRequest) throws ThingsboardException { 97 + public ObjectNode changePassword(@RequestBody JsonNode changePasswordRequest) throws ThingsboardException {
108 try { 98 try {
109 String currentPassword = changePasswordRequest.get("currentPassword").asText(); 99 String currentPassword = changePasswordRequest.get("currentPassword").asText();
110 String newPassword = changePasswordRequest.get("newPassword").asText(); 100 String newPassword = changePasswordRequest.get("newPassword").asText();
@@ -119,6 +109,12 @@ public class AuthController extends BaseController { @@ -119,6 +109,12 @@ public class AuthController extends BaseController {
119 } 109 }
120 userCredentials.setPassword(passwordEncoder.encode(newPassword)); 110 userCredentials.setPassword(passwordEncoder.encode(newPassword));
121 userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); 111 userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials);
  112 +
  113 + eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
  114 + ObjectNode response = JacksonUtil.newObjectNode();
  115 + response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken());
  116 + response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken());
  117 + return response;
122 } catch (Exception e) { 118 } catch (Exception e) {
123 throw handleException(e); 119 throw handleException(e);
124 } 120 }
@@ -135,7 +131,7 @@ public class AuthController extends BaseController { @@ -135,7 +131,7 @@ public class AuthController extends BaseController {
135 throw handleException(e); 131 throw handleException(e);
136 } 132 }
137 } 133 }
138 - 134 +
139 @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) 135 @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET)
140 public ResponseEntity<String> checkActivateToken( 136 public ResponseEntity<String> checkActivateToken(
141 @RequestParam(value = "activateToken") String activateToken) { 137 @RequestParam(value = "activateToken") String activateToken) {
@@ -157,7 +153,7 @@ public class AuthController extends BaseController { @@ -157,7 +153,7 @@ public class AuthController extends BaseController {
157 } 153 }
158 return new ResponseEntity<>(headers, responseStatus); 154 return new ResponseEntity<>(headers, responseStatus);
159 } 155 }
160 - 156 +
161 @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) 157 @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
162 @ResponseStatus(value = HttpStatus.OK) 158 @ResponseStatus(value = HttpStatus.OK)
163 public void requestResetPasswordByEmail ( 159 public void requestResetPasswordByEmail (
@@ -170,13 +166,13 @@ public class AuthController extends BaseController { @@ -170,13 +166,13 @@ public class AuthController extends BaseController {
170 String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); 166 String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
171 String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, 167 String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
172 userCredentials.getResetToken()); 168 userCredentials.getResetToken());
173 - 169 +
174 mailService.sendResetPasswordEmail(resetUrl, email); 170 mailService.sendResetPasswordEmail(resetUrl, email);
175 } catch (Exception e) { 171 } catch (Exception e) {
176 throw handleException(e); 172 throw handleException(e);
177 } 173 }
178 } 174 }
179 - 175 +
180 @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) 176 @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET)
181 public ResponseEntity<String> checkResetToken( 177 public ResponseEntity<String> checkResetToken(
182 @RequestParam(value = "resetToken") String resetToken) { 178 @RequestParam(value = "resetToken") String resetToken) {
@@ -198,7 +194,7 @@ public class AuthController extends BaseController { @@ -198,7 +194,7 @@ public class AuthController extends BaseController {
198 } 194 }
199 return new ResponseEntity<>(headers, responseStatus); 195 return new ResponseEntity<>(headers, responseStatus);
200 } 196 }
201 - 197 +
202 @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) 198 @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
203 @ResponseStatus(value = HttpStatus.OK) 199 @ResponseStatus(value = HttpStatus.OK)
204 @ResponseBody 200 @ResponseBody
@@ -240,7 +236,7 @@ public class AuthController extends BaseController { @@ -240,7 +236,7 @@ public class AuthController extends BaseController {
240 throw handleException(e); 236 throw handleException(e);
241 } 237 }
242 } 238 }
243 - 239 +
244 @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) 240 @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
245 @ResponseStatus(value = HttpStatus.OK) 241 @ResponseStatus(value = HttpStatus.OK)
246 @ResponseBody 242 @ResponseBody
@@ -268,6 +264,7 @@ public class AuthController extends BaseController { @@ -268,6 +264,7 @@ public class AuthController extends BaseController {
268 String email = user.getEmail(); 264 String email = user.getEmail();
269 mailService.sendPasswordWasResetEmail(loginUrl, email); 265 mailService.sendPasswordWasResetEmail(loginUrl, email);
270 266
  267 + eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
271 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); 268 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
272 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); 269 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
273 270
@@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode; @@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
19 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 import com.fasterxml.jackson.databind.node.ObjectNode; 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 import lombok.Getter; 21 import lombok.Getter;
22 -import org.springframework.beans.factory.annotation.Autowired; 22 +import lombok.RequiredArgsConstructor;
23 import org.springframework.beans.factory.annotation.Value; 23 import org.springframework.beans.factory.annotation.Value;
  24 +import org.springframework.context.ApplicationEventPublisher;
24 import org.springframework.http.HttpStatus; 25 import org.springframework.http.HttpStatus;
25 import org.springframework.security.access.prepost.PreAuthorize; 26 import org.springframework.security.access.prepost.PreAuthorize;
26 import org.springframework.web.bind.annotation.PathVariable; 27 import org.springframework.web.bind.annotation.PathVariable;
@@ -44,11 +45,12 @@ import org.thingsboard.server.common.data.page.PageData; @@ -44,11 +45,12 @@ import org.thingsboard.server.common.data.page.PageData;
44 import org.thingsboard.server.common.data.page.PageLink; 45 import org.thingsboard.server.common.data.page.PageLink;
45 import org.thingsboard.server.common.data.security.Authority; 46 import org.thingsboard.server.common.data.security.Authority;
46 import org.thingsboard.server.common.data.security.UserCredentials; 47 import org.thingsboard.server.common.data.security.UserCredentials;
  48 +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
  49 +import org.thingsboard.server.common.data.security.model.JwtToken;
47 import org.thingsboard.server.queue.util.TbCoreComponent; 50 import org.thingsboard.server.queue.util.TbCoreComponent;
48 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 51 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
49 import org.thingsboard.server.service.security.model.SecurityUser; 52 import org.thingsboard.server.service.security.model.SecurityUser;
50 import org.thingsboard.server.service.security.model.UserPrincipal; 53 import org.thingsboard.server.service.security.model.UserPrincipal;
51 -import org.thingsboard.server.service.security.model.token.JwtToken;  
52 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 54 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
53 import org.thingsboard.server.service.security.permission.Operation; 55 import org.thingsboard.server.service.security.permission.Operation;
54 import org.thingsboard.server.service.security.permission.Resource; 56 import org.thingsboard.server.service.security.permission.Resource;
@@ -56,6 +58,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; @@ -56,6 +58,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
56 58
57 import javax.servlet.http.HttpServletRequest; 59 import javax.servlet.http.HttpServletRequest;
58 60
  61 +@RequiredArgsConstructor
59 @RestController 62 @RestController
60 @TbCoreComponent 63 @TbCoreComponent
61 @RequestMapping("/api") 64 @RequestMapping("/api")
@@ -69,18 +72,11 @@ public class UserController extends BaseController { @@ -69,18 +72,11 @@ public class UserController extends BaseController {
69 @Getter 72 @Getter
70 private boolean userTokenAccessEnabled; 73 private boolean userTokenAccessEnabled;
71 74
72 - @Autowired  
73 - private MailService mailService;  
74 -  
75 - @Autowired  
76 - private JwtTokenFactory tokenFactory;  
77 -  
78 - @Autowired  
79 - private RefreshTokenRepository refreshTokenRepository;  
80 -  
81 - @Autowired  
82 - private SystemSecurityService systemSecurityService;  
83 - 75 + private final MailService mailService;
  76 + private final JwtTokenFactory tokenFactory;
  77 + private final RefreshTokenRepository refreshTokenRepository;
  78 + private final SystemSecurityService systemSecurityService;
  79 + private final ApplicationEventPublisher eventPublisher;
84 80
85 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 81 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
86 @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) 82 @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
@@ -341,6 +337,10 @@ public class UserController extends BaseController { @@ -341,6 +337,10 @@ public class UserController extends BaseController {
341 User user = checkUserId(userId, Operation.WRITE); 337 User user = checkUserId(userId, Operation.WRITE);
342 TenantId tenantId = getCurrentUser().getTenantId(); 338 TenantId tenantId = getCurrentUser().getTenantId();
343 userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); 339 userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled);
  340 +
  341 + if (!userCredentialsEnabled) {
  342 + eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
  343 + }
344 } catch (Exception e) { 344 } catch (Exception e) {
345 throw handleException(e); 345 throw handleException(e);
346 } 346 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth;
  17 +
  18 +import io.jsonwebtoken.Claims;
  19 +import lombok.RequiredArgsConstructor;
  20 +import org.springframework.cache.Cache;
  21 +import org.springframework.cache.CacheManager;
  22 +import org.springframework.context.event.EventListener;
  23 +import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.CacheConstants;
  25 +import org.thingsboard.server.common.data.id.UserId;
  26 +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
  27 +import org.thingsboard.server.common.data.security.model.JwtToken;
  28 +import org.thingsboard.server.config.JwtSettings;
  29 +import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
  30 +
  31 +import javax.annotation.PostConstruct;
  32 +import java.util.Optional;
  33 +
  34 +import static java.util.concurrent.TimeUnit.MILLISECONDS;
  35 +import static java.util.concurrent.TimeUnit.SECONDS;
  36 +
  37 +@Service
  38 +@RequiredArgsConstructor
  39 +public class TokenOutdatingService {
  40 + private final CacheManager cacheManager;
  41 + private final JwtTokenFactory tokenFactory;
  42 + private final JwtSettings jwtSettings;
  43 + private Cache tokenOutdatageTimeCache;
  44 +
  45 + @PostConstruct
  46 + protected void initCache() {
  47 + tokenOutdatageTimeCache = cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE);
  48 + }
  49 +
  50 + @EventListener(classes = UserAuthDataChangedEvent.class)
  51 + public void onUserAuthDataChanged(UserAuthDataChangedEvent userAuthDataChangedEvent) {
  52 + outdateOldUserTokens(userAuthDataChangedEvent.getUserId());
  53 + }
  54 +
  55 + public boolean isOutdated(JwtToken token, UserId userId) {
  56 + Claims claims = tokenFactory.parseTokenClaims(token).getBody();
  57 + long issueTime = claims.getIssuedAt().getTime();
  58 +
  59 + return Optional.ofNullable(tokenOutdatageTimeCache.get(toKey(userId), Long.class))
  60 + .map(outdatageTime -> {
  61 + if (System.currentTimeMillis() - outdatageTime <= SECONDS.toMillis(jwtSettings.getRefreshTokenExpTime())) {
  62 + return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime);
  63 + } else {
  64 + /*
  65 + * Means that since the outdating has passed more than
  66 + * the lifetime of refresh token (the longest lived)
  67 + * and there is no need to store outdatage time anymore
  68 + * as all the tokens issued before the outdatage time
  69 + * are now expired by themselves
  70 + * */
  71 + tokenOutdatageTimeCache.evict(toKey(userId));
  72 + return false;
  73 + }
  74 + })
  75 + .orElse(false);
  76 + }
  77 +
  78 + public void outdateOldUserTokens(UserId userId) {
  79 + tokenOutdatageTimeCache.put(toKey(userId), System.currentTimeMillis());
  80 + }
  81 +
  82 + private String toKey(UserId userId) {
  83 + return userId.getId().toString();
  84 + }
  85 +}
@@ -15,31 +15,34 @@ @@ -15,31 +15,34 @@
15 */ 15 */
16 package org.thingsboard.server.service.security.auth.jwt; 16 package org.thingsboard.server.service.security.auth.jwt;
17 17
18 -import org.springframework.beans.factory.annotation.Autowired; 18 +import lombok.RequiredArgsConstructor;
19 import org.springframework.security.authentication.AuthenticationProvider; 19 import org.springframework.security.authentication.AuthenticationProvider;
20 import org.springframework.security.core.Authentication; 20 import org.springframework.security.core.Authentication;
21 import org.springframework.security.core.AuthenticationException; 21 import org.springframework.security.core.AuthenticationException;
22 import org.springframework.stereotype.Component; 22 import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.service.security.auth.TokenOutdatingService;
23 import org.thingsboard.server.service.security.auth.JwtAuthenticationToken; 24 import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
  25 +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
24 import org.thingsboard.server.service.security.model.SecurityUser; 26 import org.thingsboard.server.service.security.model.SecurityUser;
25 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 27 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
26 import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; 28 import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
27 29
28 @Component 30 @Component
29 -@SuppressWarnings("unchecked") 31 +@RequiredArgsConstructor
30 public class JwtAuthenticationProvider implements AuthenticationProvider { 32 public class JwtAuthenticationProvider implements AuthenticationProvider {
31 33
32 private final JwtTokenFactory tokenFactory; 34 private final JwtTokenFactory tokenFactory;
33 -  
34 - @Autowired  
35 - public JwtAuthenticationProvider(JwtTokenFactory tokenFactory) {  
36 - this.tokenFactory = tokenFactory;  
37 - } 35 + private final TokenOutdatingService tokenOutdatingService;
38 36
39 @Override 37 @Override
40 public Authentication authenticate(Authentication authentication) throws AuthenticationException { 38 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
41 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); 39 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
42 SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken); 40 SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
  41 +
  42 + if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
  43 + throw new JwtExpiredTokenException("Token is outdated");
  44 + }
  45 +
43 return new JwtAuthenticationToken(securityUser); 46 return new JwtAuthenticationToken(securityUser);
44 } 47 }
45 48
@@ -15,9 +15,10 @@ @@ -15,9 +15,10 @@
15 */ 15 */
16 package org.thingsboard.server.service.security.auth.jwt; 16 package org.thingsboard.server.service.security.auth.jwt;
17 17
18 -import org.springframework.beans.factory.annotation.Autowired; 18 +import lombok.RequiredArgsConstructor;
19 import org.springframework.security.authentication.AuthenticationProvider; 19 import org.springframework.security.authentication.AuthenticationProvider;
20 import org.springframework.security.authentication.BadCredentialsException; 20 import org.springframework.security.authentication.BadCredentialsException;
  21 +import org.springframework.security.authentication.CredentialsExpiredException;
21 import org.springframework.security.authentication.DisabledException; 22 import org.springframework.security.authentication.DisabledException;
22 import org.springframework.security.authentication.InsufficientAuthenticationException; 23 import org.springframework.security.authentication.InsufficientAuthenticationException;
23 import org.springframework.security.core.Authentication; 24 import org.springframework.security.core.Authentication;
@@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.EntityId;
32 import org.thingsboard.server.common.data.id.TenantId; 33 import org.thingsboard.server.common.data.id.TenantId;
33 import org.thingsboard.server.common.data.id.UserId; 34 import org.thingsboard.server.common.data.id.UserId;
34 import org.thingsboard.server.common.data.security.Authority; 35 import org.thingsboard.server.common.data.security.Authority;
  36 +import org.thingsboard.server.service.security.auth.TokenOutdatingService;
35 import org.thingsboard.server.common.data.security.UserCredentials; 37 import org.thingsboard.server.common.data.security.UserCredentials;
36 import org.thingsboard.server.dao.customer.CustomerService; 38 import org.thingsboard.server.dao.customer.CustomerService;
37 import org.thingsboard.server.dao.user.UserService; 39 import org.thingsboard.server.dao.user.UserService;
@@ -44,18 +46,12 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; @@ -44,18 +46,12 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
44 import java.util.UUID; 46 import java.util.UUID;
45 47
46 @Component 48 @Component
  49 +@RequiredArgsConstructor
47 public class RefreshTokenAuthenticationProvider implements AuthenticationProvider { 50 public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
48 -  
49 private final JwtTokenFactory tokenFactory; 51 private final JwtTokenFactory tokenFactory;
50 private final UserService userService; 52 private final UserService userService;
51 private final CustomerService customerService; 53 private final CustomerService customerService;
52 -  
53 - @Autowired  
54 - public RefreshTokenAuthenticationProvider(final UserService userService, final CustomerService customerService, final JwtTokenFactory tokenFactory) {  
55 - this.userService = userService;  
56 - this.customerService = customerService;  
57 - this.tokenFactory = tokenFactory;  
58 - } 54 + private final TokenOutdatingService tokenOutdatingService;
59 55
60 @Override 56 @Override
61 public Authentication authenticate(Authentication authentication) throws AuthenticationException { 57 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
@@ -63,12 +59,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide @@ -63,12 +59,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
63 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); 59 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
64 SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken); 60 SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
65 UserPrincipal principal = unsafeUser.getUserPrincipal(); 61 UserPrincipal principal = unsafeUser.getUserPrincipal();
  62 +
66 SecurityUser securityUser; 63 SecurityUser securityUser;
67 if (principal.getType() == UserPrincipal.Type.USER_NAME) { 64 if (principal.getType() == UserPrincipal.Type.USER_NAME) {
68 securityUser = authenticateByUserId(unsafeUser.getId()); 65 securityUser = authenticateByUserId(unsafeUser.getId());
69 } else { 66 } else {
70 securityUser = authenticateByPublicId(principal.getValue()); 67 securityUser = authenticateByPublicId(principal.getValue());
71 } 68 }
  69 +
  70 + if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
  71 + throw new CredentialsExpiredException("Token is outdated");
  72 + }
  73 +
72 return new RefreshAuthenticationToken(securityUser); 74 return new RefreshAuthenticationToken(securityUser);
73 } 75 }
74 76
@@ -91,7 +93,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide @@ -91,7 +93,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
91 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned"); 93 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
92 94
93 UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); 95 UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
94 -  
95 SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal); 96 SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
96 97
97 return securityUser; 98 return securityUser;
@@ -17,8 +17,8 @@ package org.thingsboard.server.service.security.auth.jwt; @@ -17,8 +17,8 @@ package org.thingsboard.server.service.security.auth.jwt;
17 17
18 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.stereotype.Component; 19 import org.springframework.stereotype.Component;
  20 +import org.thingsboard.server.common.data.security.model.JwtToken;
20 import org.thingsboard.server.service.security.model.SecurityUser; 21 import org.thingsboard.server.service.security.model.SecurityUser;
21 -import org.thingsboard.server.service.security.model.token.JwtToken;  
22 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 22 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
23 23
24 @Component 24 @Component
@@ -26,13 +26,12 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -26,13 +26,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
27 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; 28 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
  29 +import org.thingsboard.server.common.data.security.model.JwtToken;
29 import org.thingsboard.server.dao.oauth2.OAuth2Service; 30 import org.thingsboard.server.dao.oauth2.OAuth2Service;
30 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 31 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
31 import org.thingsboard.server.service.security.model.SecurityUser; 32 import org.thingsboard.server.service.security.model.SecurityUser;
32 -import org.thingsboard.server.service.security.model.token.JwtToken;  
33 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 33 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
34 import org.thingsboard.server.service.security.system.SystemSecurityService; 34 import org.thingsboard.server.service.security.system.SystemSecurityService;
35 -import org.thingsboard.server.utils.MiscUtils;  
36 35
37 import javax.servlet.http.HttpServletRequest; 36 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse; 37 import javax.servlet.http.HttpServletResponse;
@@ -23,9 +23,9 @@ import org.springframework.security.core.Authentication; @@ -23,9 +23,9 @@ import org.springframework.security.core.Authentication;
23 import org.springframework.security.web.WebAttributes; 23 import org.springframework.security.web.WebAttributes;
24 import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 24 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
25 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
  26 +import org.thingsboard.server.common.data.security.model.JwtToken;
26 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 27 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
27 import org.thingsboard.server.service.security.model.SecurityUser; 28 import org.thingsboard.server.service.security.model.SecurityUser;
28 -import org.thingsboard.server.service.security.model.token.JwtToken;  
29 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 29 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
30 30
31 import javax.servlet.ServletException; 31 import javax.servlet.ServletException;
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 package org.thingsboard.server.service.security.exception; 16 package org.thingsboard.server.service.security.exception;
17 17
18 import org.springframework.security.core.AuthenticationException; 18 import org.springframework.security.core.AuthenticationException;
19 -import org.thingsboard.server.service.security.model.token.JwtToken; 19 +import org.thingsboard.server.common.data.security.model.JwtToken;
20 20
21 public class JwtExpiredTokenException extends AuthenticationException { 21 public class JwtExpiredTokenException extends AuthenticationException {
22 private static final long serialVersionUID = -5959543783324224864L; 22 private static final long serialVersionUID = -5959543783324224864L;
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.model.token; @@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.model.token;
17 17
18 import com.fasterxml.jackson.annotation.JsonIgnore; 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 import io.jsonwebtoken.Claims; 19 import io.jsonwebtoken.Claims;
  20 +import org.thingsboard.server.common.data.security.model.JwtToken;
20 21
21 public final class AccessJwtToken implements JwtToken { 22 public final class AccessJwtToken implements JwtToken {
22 private final String rawToken; 23 private final String rawToken;
@@ -16,18 +16,26 @@ @@ -16,18 +16,26 @@
16 package org.thingsboard.server.service.security.model.token; 16 package org.thingsboard.server.service.security.model.token;
17 17
18 import io.jsonwebtoken.Claims; 18 import io.jsonwebtoken.Claims;
  19 +import io.jsonwebtoken.ExpiredJwtException;
19 import io.jsonwebtoken.Jws; 20 import io.jsonwebtoken.Jws;
20 import io.jsonwebtoken.Jwts; 21 import io.jsonwebtoken.Jwts;
  22 +import io.jsonwebtoken.MalformedJwtException;
21 import io.jsonwebtoken.SignatureAlgorithm; 23 import io.jsonwebtoken.SignatureAlgorithm;
  24 +import io.jsonwebtoken.SignatureException;
  25 +import io.jsonwebtoken.UnsupportedJwtException;
  26 +import lombok.extern.slf4j.Slf4j;
22 import org.apache.commons.lang3.StringUtils; 27 import org.apache.commons.lang3.StringUtils;
23 import org.springframework.beans.factory.annotation.Autowired; 28 import org.springframework.beans.factory.annotation.Autowired;
  29 +import org.springframework.security.authentication.BadCredentialsException;
24 import org.springframework.security.core.GrantedAuthority; 30 import org.springframework.security.core.GrantedAuthority;
25 import org.springframework.stereotype.Component; 31 import org.springframework.stereotype.Component;
26 import org.thingsboard.server.common.data.id.CustomerId; 32 import org.thingsboard.server.common.data.id.CustomerId;
27 import org.thingsboard.server.common.data.id.TenantId; 33 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.common.data.id.UserId; 34 import org.thingsboard.server.common.data.id.UserId;
29 import org.thingsboard.server.common.data.security.Authority; 35 import org.thingsboard.server.common.data.security.Authority;
  36 +import org.thingsboard.server.common.data.security.model.JwtToken;
30 import org.thingsboard.server.config.JwtSettings; 37 import org.thingsboard.server.config.JwtSettings;
  38 +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
31 import org.thingsboard.server.service.security.model.SecurityUser; 39 import org.thingsboard.server.service.security.model.SecurityUser;
32 import org.thingsboard.server.service.security.model.UserPrincipal; 40 import org.thingsboard.server.service.security.model.UserPrincipal;
33 41
@@ -39,6 +47,7 @@ import java.util.UUID; @@ -39,6 +47,7 @@ import java.util.UUID;
39 import java.util.stream.Collectors; 47 import java.util.stream.Collectors;
40 48
41 @Component 49 @Component
  50 +@Slf4j
42 public class JwtTokenFactory { 51 public class JwtTokenFactory {
43 52
44 private static final String SCOPES = "scopes"; 53 private static final String SCOPES = "scopes";
@@ -97,7 +106,7 @@ public class JwtTokenFactory { @@ -97,7 +106,7 @@ public class JwtTokenFactory {
97 } 106 }
98 107
99 public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) { 108 public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) {
100 - Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); 109 + Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
101 Claims claims = jwsClaims.getBody(); 110 Claims claims = jwsClaims.getBody();
102 String subject = claims.getSubject(); 111 String subject = claims.getSubject();
103 @SuppressWarnings("unchecked") 112 @SuppressWarnings("unchecked")
@@ -153,7 +162,7 @@ public class JwtTokenFactory { @@ -153,7 +162,7 @@ public class JwtTokenFactory {
153 } 162 }
154 163
155 public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) { 164 public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) {
156 - Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); 165 + Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
157 Claims claims = jwsClaims.getBody(); 166 Claims claims = jwsClaims.getBody();
158 String subject = claims.getSubject(); 167 String subject = claims.getSubject();
159 @SuppressWarnings("unchecked") 168 @SuppressWarnings("unchecked")
@@ -171,4 +180,17 @@ public class JwtTokenFactory { @@ -171,4 +180,17 @@ public class JwtTokenFactory {
171 return securityUser; 180 return securityUser;
172 } 181 }
173 182
  183 + public Jws<Claims> parseTokenClaims(JwtToken token) {
  184 + try {
  185 + return Jwts.parser()
  186 + .setSigningKey(settings.getTokenSigningKey())
  187 + .parseClaimsJws(token.getToken());
  188 + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
  189 + log.debug("Invalid JWT Token", ex);
  190 + throw new BadCredentialsException("Invalid JWT token: ", ex);
  191 + } catch (ExpiredJwtException expiredEx) {
  192 + log.debug("JWT Token is expired", expiredEx);
  193 + throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx);
  194 + }
  195 + }
174 } 196 }
@@ -15,22 +15,10 @@ @@ -15,22 +15,10 @@
15 */ 15 */
16 package org.thingsboard.server.service.security.model.token; 16 package org.thingsboard.server.service.security.model.token;
17 17
18 -import io.jsonwebtoken.Claims;  
19 -import io.jsonwebtoken.ExpiredJwtException;  
20 -import io.jsonwebtoken.Jws;  
21 -import io.jsonwebtoken.Jwts;  
22 -import io.jsonwebtoken.MalformedJwtException;  
23 -import io.jsonwebtoken.SignatureException;  
24 -import io.jsonwebtoken.UnsupportedJwtException;  
25 -import lombok.extern.slf4j.Slf4j;  
26 -import org.slf4j.Logger;  
27 -import org.slf4j.LoggerFactory;  
28 -import org.springframework.security.authentication.BadCredentialsException;  
29 -import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; 18 +import org.thingsboard.server.common.data.security.model.JwtToken;
30 19
31 import java.io.Serializable; 20 import java.io.Serializable;
32 21
33 -@Slf4j  
34 public class RawAccessJwtToken implements JwtToken, Serializable { 22 public class RawAccessJwtToken implements JwtToken, Serializable {
35 23
36 private static final long serialVersionUID = -797397445703066079L; 24 private static final long serialVersionUID = -797397445703066079L;
@@ -41,25 +29,6 @@ public class RawAccessJwtToken implements JwtToken, Serializable { @@ -41,25 +29,6 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
41 this.token = token; 29 this.token = token;
42 } 30 }
43 31
44 - /**  
45 - * Parses and validates JWT Token signature.  
46 - *  
47 - * @throws BadCredentialsException  
48 - * @throws JwtExpiredTokenException  
49 - *  
50 - */  
51 - public Jws<Claims> parseClaims(String signingKey) {  
52 - try {  
53 - return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);  
54 - } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {  
55 - log.debug("Invalid JWT Token", ex);  
56 - throw new BadCredentialsException("Invalid JWT token: ", ex);  
57 - } catch (ExpiredJwtException expiredEx) {  
58 - log.debug("JWT Token is expired", expiredEx);  
59 - throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);  
60 - }  
61 - }  
62 -  
63 @Override 32 @Override
64 public String getToken() { 33 public String getToken() {
65 return token; 34 return token;
@@ -362,6 +362,9 @@ caffeine: @@ -362,6 +362,9 @@ caffeine:
362 attributes: 362 attributes:
363 timeToLiveInMinutes: 1440 363 timeToLiveInMinutes: 1440
364 maxSize: 100000 364 maxSize: 100000
  365 + tokensOutdatageTime:
  366 + timeToLiveInMinutes: 20000
  367 + maxSize: 10000
365 368
366 redis: 369 redis:
367 # standalone or cluster 370 # standalone or cluster
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth;
  17 +
  18 +import org.junit.jupiter.api.BeforeEach;
  19 +import org.junit.jupiter.api.Test;
  20 +import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
  21 +import org.springframework.security.authentication.CredentialsExpiredException;
  22 +import org.thingsboard.server.common.data.CacheConstants;
  23 +import org.thingsboard.server.common.data.User;
  24 +import org.thingsboard.server.common.data.id.UserId;
  25 +import org.thingsboard.server.common.data.security.Authority;
  26 +import org.thingsboard.server.common.data.security.UserCredentials;
  27 +import org.thingsboard.server.common.data.security.model.JwtToken;
  28 +import org.thingsboard.server.config.JwtSettings;
  29 +import org.thingsboard.server.dao.customer.CustomerService;
  30 +import org.thingsboard.server.dao.user.UserService;
  31 +import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
  32 +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
  33 +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
  34 +import org.thingsboard.server.service.security.model.SecurityUser;
  35 +import org.thingsboard.server.service.security.model.UserPrincipal;
  36 +import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
  37 +import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
  38 +
  39 +import java.util.UUID;
  40 +
  41 +import static java.util.concurrent.TimeUnit.DAYS;
  42 +import static java.util.concurrent.TimeUnit.MINUTES;
  43 +import static java.util.concurrent.TimeUnit.SECONDS;
  44 +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
  45 +import static org.junit.jupiter.api.Assertions.assertFalse;
  46 +import static org.junit.jupiter.api.Assertions.assertNotNull;
  47 +import static org.junit.jupiter.api.Assertions.assertNull;
  48 +import static org.junit.jupiter.api.Assertions.assertThrows;
  49 +import static org.junit.jupiter.api.Assertions.assertTrue;
  50 +import static org.mockito.ArgumentMatchers.any;
  51 +import static org.mockito.ArgumentMatchers.eq;
  52 +import static org.mockito.Mockito.mock;
  53 +import static org.mockito.Mockito.when;
  54 +
  55 +public class TokenOutdatingTest {
  56 + private JwtAuthenticationProvider accessTokenAuthenticationProvider;
  57 + private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
  58 +
  59 + private TokenOutdatingService tokenOutdatingService;
  60 + private ConcurrentMapCacheManager cacheManager;
  61 + private JwtTokenFactory tokenFactory;
  62 + private JwtSettings jwtSettings;
  63 +
  64 + private UserId userId;
  65 +
  66 + @BeforeEach
  67 + public void setUp() {
  68 + jwtSettings = new JwtSettings();
  69 + jwtSettings.setTokenIssuer("test.io");
  70 + jwtSettings.setTokenExpirationTime((int) MINUTES.toSeconds(10));
  71 + jwtSettings.setRefreshTokenExpTime((int) DAYS.toSeconds(7));
  72 + jwtSettings.setTokenSigningKey("secret");
  73 + tokenFactory = new JwtTokenFactory(jwtSettings);
  74 +
  75 + cacheManager = new ConcurrentMapCacheManager();
  76 + tokenOutdatingService = new TokenOutdatingService(cacheManager, tokenFactory, jwtSettings);
  77 + tokenOutdatingService.initCache();
  78 +
  79 + userId = new UserId(UUID.randomUUID());
  80 +
  81 + UserService userService = mock(UserService.class);
  82 +
  83 + User user = new User();
  84 + user.setId(userId);
  85 + user.setAuthority(Authority.TENANT_ADMIN);
  86 + user.setEmail("email");
  87 + when(userService.findUserById(any(), eq(userId))).thenReturn(user);
  88 +
  89 + UserCredentials userCredentials = new UserCredentials();
  90 + userCredentials.setEnabled(true);
  91 + when(userService.findUserCredentialsByUserId(any(), eq(userId))).thenReturn(userCredentials);
  92 +
  93 + accessTokenAuthenticationProvider = new JwtAuthenticationProvider(tokenFactory, tokenOutdatingService);
  94 + refreshTokenAuthenticationProvider = new RefreshTokenAuthenticationProvider(tokenFactory, userService, mock(CustomerService.class), tokenOutdatingService);
  95 + }
  96 +
  97 + @Test
  98 + public void testOutdateOldUserTokens() throws Exception {
  99 + JwtToken jwtToken = createAccessJwtToken(userId);
  100 +
  101 + SECONDS.sleep(1); // need to wait before outdating so that outdatage time is strictly after token issue time
  102 + tokenOutdatingService.outdateOldUserTokens(userId);
  103 + assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
  104 +
  105 + SECONDS.sleep(1);
  106 +
  107 + JwtToken newJwtToken = tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
  108 + assertFalse(tokenOutdatingService.isOutdated(newJwtToken, userId));
  109 + }
  110 +
  111 + @Test
  112 + public void testAuthenticateWithOutdatedAccessToken() throws InterruptedException {
  113 + RawAccessJwtToken accessJwtToken = getRawJwtToken(createAccessJwtToken(userId));
  114 +
  115 + assertDoesNotThrow(() -> {
  116 + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
  117 + });
  118 +
  119 + SECONDS.sleep(1);
  120 + tokenOutdatingService.outdateOldUserTokens(userId);
  121 +
  122 + assertThrows(JwtExpiredTokenException.class, () -> {
  123 + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
  124 + });
  125 + }
  126 +
  127 + @Test
  128 + public void testAuthenticateWithOutdatedRefreshToken() throws InterruptedException {
  129 + RawAccessJwtToken refreshJwtToken = getRawJwtToken(createRefreshJwtToken(userId));
  130 +
  131 + assertDoesNotThrow(() -> {
  132 + refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
  133 + });
  134 +
  135 + SECONDS.sleep(1);
  136 + tokenOutdatingService.outdateOldUserTokens(userId);
  137 +
  138 + assertThrows(CredentialsExpiredException.class, () -> {
  139 + refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
  140 + });
  141 + }
  142 +
  143 + @Test
  144 + public void testTokensOutdatageTimeRemovalFromCache() throws Exception {
  145 + JwtToken jwtToken = createAccessJwtToken(userId);
  146 +
  147 + SECONDS.sleep(1);
  148 + tokenOutdatingService.outdateOldUserTokens(userId);
  149 +
  150 + int refreshTokenExpirationTime = 5;
  151 + jwtSettings.setRefreshTokenExpTime(refreshTokenExpirationTime);
  152 +
  153 + SECONDS.sleep(refreshTokenExpirationTime - 2);
  154 +
  155 + assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
  156 + assertNotNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
  157 +
  158 + SECONDS.sleep(3);
  159 +
  160 + assertFalse(tokenOutdatingService.isOutdated(jwtToken, userId));
  161 + assertNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
  162 + }
  163 +
  164 + private JwtToken createAccessJwtToken(UserId userId) {
  165 + return tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
  166 + }
  167 +
  168 + private JwtToken createRefreshJwtToken(UserId userId) {
  169 + return tokenFactory.createRefreshToken(createMockSecurityUser(userId));
  170 + }
  171 +
  172 + private RawAccessJwtToken getRawJwtToken(JwtToken token) {
  173 + return new RawAccessJwtToken(token.getToken());
  174 + }
  175 +
  176 + private SecurityUser createMockSecurityUser(UserId userId) {
  177 + SecurityUser securityUser = new SecurityUser();
  178 + securityUser.setEmail("email");
  179 + securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail()));
  180 + securityUser.setAuthority(Authority.CUSTOMER_USER);
  181 + securityUser.setId(userId);
  182 + return securityUser;
  183 + }
  184 +}
@@ -27,4 +27,5 @@ public class CacheConstants { @@ -27,4 +27,5 @@ public class CacheConstants {
27 public static final String TENANT_PROFILE_CACHE = "tenantProfiles"; 27 public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; 28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
29 public static final String ATTRIBUTES_CACHE = "attributes"; 29 public static final String ATTRIBUTES_CACHE = "attributes";
  30 + public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
30 } 31 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.security.event;
  17 +
  18 +import org.thingsboard.server.common.data.id.UserId;
  19 +
  20 +public class UserAuthDataChangedEvent {
  21 + private final UserId userId;
  22 +
  23 + public UserAuthDataChangedEvent(UserId userId) {
  24 + this.userId = userId;
  25 + }
  26 +
  27 + public UserId getUserId() {
  28 + return userId;
  29 + }
  30 +}
common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtToken.java renamed from application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -package org.thingsboard.server.service.security.model.token; 16 +package org.thingsboard.server.common.data.security.model;
17 17
18 import java.io.Serializable; 18 import java.io.Serializable;
19 19
@@ -22,8 +22,8 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -22,8 +22,8 @@ import com.google.common.util.concurrent.ListenableFuture;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
23 import org.apache.commons.lang3.RandomStringUtils; 23 import org.apache.commons.lang3.RandomStringUtils;
24 import org.apache.commons.lang3.StringUtils; 24 import org.apache.commons.lang3.StringUtils;
25 -import org.springframework.beans.factory.annotation.Autowired;  
26 import org.springframework.beans.factory.annotation.Value; 25 import org.springframework.beans.factory.annotation.Value;
  26 +import org.springframework.context.ApplicationEventPublisher;
27 import org.springframework.context.annotation.Lazy; 27 import org.springframework.context.annotation.Lazy;
28 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
29 import org.thingsboard.server.common.data.Customer; 29 import org.thingsboard.server.common.data.Customer;
@@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData; @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData;
38 import org.thingsboard.server.common.data.page.PageLink; 38 import org.thingsboard.server.common.data.page.PageLink;
39 import org.thingsboard.server.common.data.security.Authority; 39 import org.thingsboard.server.common.data.security.Authority;
40 import org.thingsboard.server.common.data.security.UserCredentials; 40 import org.thingsboard.server.common.data.security.UserCredentials;
  41 +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
41 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 42 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
42 import org.thingsboard.server.dao.customer.CustomerDao; 43 import org.thingsboard.server.dao.customer.CustomerDao;
43 import org.thingsboard.server.dao.entity.AbstractEntityService; 44 import org.thingsboard.server.dao.entity.AbstractEntityService;
@@ -75,21 +76,26 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic @@ -75,21 +76,26 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
75 @Value("${security.user_login_case_sensitive:true}") 76 @Value("${security.user_login_case_sensitive:true}")
76 private boolean userLoginCaseSensitive; 77 private boolean userLoginCaseSensitive;
77 78
78 - @Autowired  
79 - private UserDao userDao;  
80 -  
81 - @Autowired  
82 - private UserCredentialsDao userCredentialsDao;  
83 -  
84 - @Autowired  
85 - private TenantDao tenantDao;  
86 -  
87 - @Autowired  
88 - private CustomerDao customerDao;  
89 -  
90 - @Autowired  
91 - @Lazy  
92 - private TbTenantProfileCache tenantProfileCache; 79 + private final UserDao userDao;
  80 + private final UserCredentialsDao userCredentialsDao;
  81 + private final TenantDao tenantDao;
  82 + private final CustomerDao customerDao;
  83 + private final TbTenantProfileCache tenantProfileCache;
  84 + private final ApplicationEventPublisher eventPublisher;
  85 +
  86 + public UserServiceImpl(UserDao userDao,
  87 + UserCredentialsDao userCredentialsDao,
  88 + TenantDao tenantDao,
  89 + CustomerDao customerDao,
  90 + @Lazy TbTenantProfileCache tenantProfileCache,
  91 + ApplicationEventPublisher eventPublisher) {
  92 + this.userDao = userDao;
  93 + this.userCredentialsDao = userCredentialsDao;
  94 + this.tenantDao = tenantDao;
  95 + this.customerDao = customerDao;
  96 + this.tenantProfileCache = tenantProfileCache;
  97 + this.eventPublisher = eventPublisher;
  98 + }
93 99
94 @Override 100 @Override
95 public User findUserByEmail(TenantId tenantId, String email) { 101 public User findUserByEmail(TenantId tenantId, String email) {
@@ -225,6 +231,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic @@ -225,6 +231,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
225 userCredentialsDao.removeById(tenantId, userCredentials.getUuidId()); 231 userCredentialsDao.removeById(tenantId, userCredentials.getUuidId());
226 deleteEntityRelations(tenantId, userId); 232 deleteEntityRelations(tenantId, userId);
227 userDao.removeById(tenantId, userId.getId()); 233 userDao.removeById(tenantId, userId.getId());
  234 + eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
228 } 235 }
229 236
230 @Override 237 @Override
@@ -149,8 +149,11 @@ export class AuthService { @@ -149,8 +149,11 @@ export class AuthService {
149 } 149 }
150 150
151 public changePassword(currentPassword: string, newPassword: string) { 151 public changePassword(currentPassword: string, newPassword: string) {
152 - return this.http.post('/api/auth/changePassword',  
153 - {currentPassword, newPassword}, defaultHttpOptions()); 152 + return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptions()).pipe(
  153 + tap((loginResponse: LoginResponse) => {
  154 + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
  155 + }
  156 + ));
154 } 157 }
155 158
156 public activateByEmailCode(emailCode: string): Observable<LoginResponse> { 159 public activateByEmailCode(emailCode: string): Observable<LoginResponse> {