Commit bc6efa5e1e339b07e8b20ac2b59d26eb2713cd4f
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 | 17 | |
18 | 18 | import org.springframework.boot.context.properties.ConfigurationProperties; |
19 | 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 | 22 | @Configuration |
23 | 23 | @ConfigurationProperties(prefix = "security.jwt") | ... | ... |
... | ... | @@ -18,8 +18,9 @@ package org.thingsboard.server.controller; |
18 | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | +import lombok.RequiredArgsConstructor; | |
21 | 22 | import lombok.extern.slf4j.Slf4j; |
22 | -import org.springframework.beans.factory.annotation.Autowired; | |
23 | +import org.springframework.context.ApplicationEventPublisher; | |
23 | 24 | import org.springframework.http.HttpHeaders; |
24 | 25 | import org.springframework.http.HttpStatus; |
25 | 26 | import org.springframework.http.ResponseEntity; |
... | ... | @@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RequestParam; |
32 | 33 | import org.springframework.web.bind.annotation.ResponseBody; |
33 | 34 | import org.springframework.web.bind.annotation.ResponseStatus; |
34 | 35 | import org.springframework.web.bind.annotation.RestController; |
36 | +import org.thingsboard.common.util.JacksonUtil; | |
35 | 37 | import org.thingsboard.rule.engine.api.MailService; |
36 | 38 | import org.thingsboard.server.common.data.User; |
37 | 39 | import org.thingsboard.server.common.data.audit.ActionType; |
... | ... | @@ -39,48 +41,37 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
39 | 41 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
40 | 42 | import org.thingsboard.server.common.data.id.TenantId; |
41 | 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 | 48 | import org.thingsboard.server.dao.audit.AuditLogService; |
43 | 49 | import org.thingsboard.server.queue.util.TbCoreComponent; |
44 | 50 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
45 | 51 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; |
46 | -import org.thingsboard.server.common.data.security.model.SecuritySettings; | |
47 | 52 | import org.thingsboard.server.service.security.model.SecurityUser; |
48 | -import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; | |
49 | 53 | import org.thingsboard.server.service.security.model.UserPrincipal; |
50 | -import org.thingsboard.server.service.security.model.token.JwtToken; | |
51 | 54 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
52 | 55 | import org.thingsboard.server.service.security.system.SystemSecurityService; |
53 | -import org.thingsboard.server.utils.MiscUtils; | |
54 | 56 | import ua_parser.Client; |
55 | 57 | |
56 | 58 | import javax.servlet.http.HttpServletRequest; |
57 | 59 | import java.net.URI; |
58 | 60 | import java.net.URISyntaxException; |
59 | -import java.util.List; | |
60 | 61 | |
61 | 62 | @RestController |
62 | 63 | @TbCoreComponent |
63 | 64 | @RequestMapping("/api") |
64 | 65 | @Slf4j |
66 | +@RequiredArgsConstructor | |
65 | 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 | 76 | @PreAuthorize("isAuthenticated()") |
86 | 77 | @RequestMapping(value = "/auth/user", method = RequestMethod.GET) |
... | ... | @@ -103,8 +94,7 @@ public class AuthController extends BaseController { |
103 | 94 | @PreAuthorize("isAuthenticated()") |
104 | 95 | @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) |
105 | 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 | 98 | try { |
109 | 99 | String currentPassword = changePasswordRequest.get("currentPassword").asText(); |
110 | 100 | String newPassword = changePasswordRequest.get("newPassword").asText(); |
... | ... | @@ -119,6 +109,12 @@ public class AuthController extends BaseController { |
119 | 109 | } |
120 | 110 | userCredentials.setPassword(passwordEncoder.encode(newPassword)); |
121 | 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 | 118 | } catch (Exception e) { |
123 | 119 | throw handleException(e); |
124 | 120 | } |
... | ... | @@ -135,7 +131,7 @@ public class AuthController extends BaseController { |
135 | 131 | throw handleException(e); |
136 | 132 | } |
137 | 133 | } |
138 | - | |
134 | + | |
139 | 135 | @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) |
140 | 136 | public ResponseEntity<String> checkActivateToken( |
141 | 137 | @RequestParam(value = "activateToken") String activateToken) { |
... | ... | @@ -157,7 +153,7 @@ public class AuthController extends BaseController { |
157 | 153 | } |
158 | 154 | return new ResponseEntity<>(headers, responseStatus); |
159 | 155 | } |
160 | - | |
156 | + | |
161 | 157 | @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) |
162 | 158 | @ResponseStatus(value = HttpStatus.OK) |
163 | 159 | public void requestResetPasswordByEmail ( |
... | ... | @@ -170,13 +166,13 @@ public class AuthController extends BaseController { |
170 | 166 | String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); |
171 | 167 | String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, |
172 | 168 | userCredentials.getResetToken()); |
173 | - | |
169 | + | |
174 | 170 | mailService.sendResetPasswordEmail(resetUrl, email); |
175 | 171 | } catch (Exception e) { |
176 | 172 | throw handleException(e); |
177 | 173 | } |
178 | 174 | } |
179 | - | |
175 | + | |
180 | 176 | @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) |
181 | 177 | public ResponseEntity<String> checkResetToken( |
182 | 178 | @RequestParam(value = "resetToken") String resetToken) { |
... | ... | @@ -198,7 +194,7 @@ public class AuthController extends BaseController { |
198 | 194 | } |
199 | 195 | return new ResponseEntity<>(headers, responseStatus); |
200 | 196 | } |
201 | - | |
197 | + | |
202 | 198 | @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) |
203 | 199 | @ResponseStatus(value = HttpStatus.OK) |
204 | 200 | @ResponseBody |
... | ... | @@ -240,7 +236,7 @@ public class AuthController extends BaseController { |
240 | 236 | throw handleException(e); |
241 | 237 | } |
242 | 238 | } |
243 | - | |
239 | + | |
244 | 240 | @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) |
245 | 241 | @ResponseStatus(value = HttpStatus.OK) |
246 | 242 | @ResponseBody |
... | ... | @@ -268,6 +264,7 @@ public class AuthController extends BaseController { |
268 | 264 | String email = user.getEmail(); |
269 | 265 | mailService.sendPasswordWasResetEmail(loginUrl, email); |
270 | 266 | |
267 | + eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId())); | |
271 | 268 | JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); |
272 | 269 | JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); |
273 | 270 | ... | ... |
... | ... | @@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | 21 | import lombok.Getter; |
22 | -import org.springframework.beans.factory.annotation.Autowired; | |
22 | +import lombok.RequiredArgsConstructor; | |
23 | 23 | import org.springframework.beans.factory.annotation.Value; |
24 | +import org.springframework.context.ApplicationEventPublisher; | |
24 | 25 | import org.springframework.http.HttpStatus; |
25 | 26 | import org.springframework.security.access.prepost.PreAuthorize; |
26 | 27 | import org.springframework.web.bind.annotation.PathVariable; |
... | ... | @@ -44,11 +45,12 @@ import org.thingsboard.server.common.data.page.PageData; |
44 | 45 | import org.thingsboard.server.common.data.page.PageLink; |
45 | 46 | import org.thingsboard.server.common.data.security.Authority; |
46 | 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 | 50 | import org.thingsboard.server.queue.util.TbCoreComponent; |
48 | 51 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
49 | 52 | import org.thingsboard.server.service.security.model.SecurityUser; |
50 | 53 | import org.thingsboard.server.service.security.model.UserPrincipal; |
51 | -import org.thingsboard.server.service.security.model.token.JwtToken; | |
52 | 54 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
53 | 55 | import org.thingsboard.server.service.security.permission.Operation; |
54 | 56 | import org.thingsboard.server.service.security.permission.Resource; |
... | ... | @@ -56,6 +58,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; |
56 | 58 | |
57 | 59 | import javax.servlet.http.HttpServletRequest; |
58 | 60 | |
61 | +@RequiredArgsConstructor | |
59 | 62 | @RestController |
60 | 63 | @TbCoreComponent |
61 | 64 | @RequestMapping("/api") |
... | ... | @@ -69,18 +72,11 @@ public class UserController extends BaseController { |
69 | 72 | @Getter |
70 | 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 | 81 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
86 | 82 | @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) |
... | ... | @@ -341,6 +337,10 @@ public class UserController extends BaseController { |
341 | 337 | User user = checkUserId(userId, Operation.WRITE); |
342 | 338 | TenantId tenantId = getCurrentUser().getTenantId(); |
343 | 339 | userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); |
340 | + | |
341 | + if (!userCredentialsEnabled) { | |
342 | + eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId)); | |
343 | + } | |
344 | 344 | } catch (Exception e) { |
345 | 345 | throw handleException(e); |
346 | 346 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
0 → 100644
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 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.security.auth.jwt; |
17 | 17 | |
18 | -import org.springframework.beans.factory.annotation.Autowired; | |
18 | +import lombok.RequiredArgsConstructor; | |
19 | 19 | import org.springframework.security.authentication.AuthenticationProvider; |
20 | 20 | import org.springframework.security.core.Authentication; |
21 | 21 | import org.springframework.security.core.AuthenticationException; |
22 | 22 | import org.springframework.stereotype.Component; |
23 | +import org.thingsboard.server.service.security.auth.TokenOutdatingService; | |
23 | 24 | import org.thingsboard.server.service.security.auth.JwtAuthenticationToken; |
25 | +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; | |
24 | 26 | import org.thingsboard.server.service.security.model.SecurityUser; |
25 | 27 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
26 | 28 | import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; |
27 | 29 | |
28 | 30 | @Component |
29 | -@SuppressWarnings("unchecked") | |
31 | +@RequiredArgsConstructor | |
30 | 32 | public class JwtAuthenticationProvider implements AuthenticationProvider { |
31 | 33 | |
32 | 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 | 37 | @Override |
40 | 38 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
41 | 39 | RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); |
42 | 40 | SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken); |
41 | + | |
42 | + if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) { | |
43 | + throw new JwtExpiredTokenException("Token is outdated"); | |
44 | + } | |
45 | + | |
43 | 46 | return new JwtAuthenticationToken(securityUser); |
44 | 47 | } |
45 | 48 | ... | ... |
... | ... | @@ -15,9 +15,10 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.security.auth.jwt; |
17 | 17 | |
18 | -import org.springframework.beans.factory.annotation.Autowired; | |
18 | +import lombok.RequiredArgsConstructor; | |
19 | 19 | import org.springframework.security.authentication.AuthenticationProvider; |
20 | 20 | import org.springframework.security.authentication.BadCredentialsException; |
21 | +import org.springframework.security.authentication.CredentialsExpiredException; | |
21 | 22 | import org.springframework.security.authentication.DisabledException; |
22 | 23 | import org.springframework.security.authentication.InsufficientAuthenticationException; |
23 | 24 | import org.springframework.security.core.Authentication; |
... | ... | @@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.EntityId; |
32 | 33 | import org.thingsboard.server.common.data.id.TenantId; |
33 | 34 | import org.thingsboard.server.common.data.id.UserId; |
34 | 35 | import org.thingsboard.server.common.data.security.Authority; |
36 | +import org.thingsboard.server.service.security.auth.TokenOutdatingService; | |
35 | 37 | import org.thingsboard.server.common.data.security.UserCredentials; |
36 | 38 | import org.thingsboard.server.dao.customer.CustomerService; |
37 | 39 | import org.thingsboard.server.dao.user.UserService; |
... | ... | @@ -44,18 +46,12 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; |
44 | 46 | import java.util.UUID; |
45 | 47 | |
46 | 48 | @Component |
49 | +@RequiredArgsConstructor | |
47 | 50 | public class RefreshTokenAuthenticationProvider implements AuthenticationProvider { |
48 | - | |
49 | 51 | private final JwtTokenFactory tokenFactory; |
50 | 52 | private final UserService userService; |
51 | 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 | 56 | @Override |
61 | 57 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
... | ... | @@ -63,12 +59,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide |
63 | 59 | RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); |
64 | 60 | SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken); |
65 | 61 | UserPrincipal principal = unsafeUser.getUserPrincipal(); |
62 | + | |
66 | 63 | SecurityUser securityUser; |
67 | 64 | if (principal.getType() == UserPrincipal.Type.USER_NAME) { |
68 | 65 | securityUser = authenticateByUserId(unsafeUser.getId()); |
69 | 66 | } else { |
70 | 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 | 74 | return new RefreshAuthenticationToken(securityUser); |
73 | 75 | } |
74 | 76 | |
... | ... | @@ -91,7 +93,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide |
91 | 93 | if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned"); |
92 | 94 | |
93 | 95 | UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); |
94 | - | |
95 | 96 | SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal); |
96 | 97 | |
97 | 98 | return securityUser; | ... | ... |
... | ... | @@ -17,8 +17,8 @@ package org.thingsboard.server.service.security.auth.jwt; |
17 | 17 | |
18 | 18 | import org.springframework.beans.factory.annotation.Autowired; |
19 | 19 | import org.springframework.stereotype.Component; |
20 | +import org.thingsboard.server.common.data.security.model.JwtToken; | |
20 | 21 | import org.thingsboard.server.service.security.model.SecurityUser; |
21 | -import org.thingsboard.server.service.security.model.token.JwtToken; | |
22 | 22 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
23 | 23 | |
24 | 24 | @Component | ... | ... |
... | ... | @@ -26,13 +26,12 @@ import org.thingsboard.server.common.data.id.CustomerId; |
26 | 26 | import org.thingsboard.server.common.data.id.EntityId; |
27 | 27 | import org.thingsboard.server.common.data.id.TenantId; |
28 | 28 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; |
29 | +import org.thingsboard.server.common.data.security.model.JwtToken; | |
29 | 30 | import org.thingsboard.server.dao.oauth2.OAuth2Service; |
30 | 31 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
31 | 32 | import org.thingsboard.server.service.security.model.SecurityUser; |
32 | -import org.thingsboard.server.service.security.model.token.JwtToken; | |
33 | 33 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
34 | 34 | import org.thingsboard.server.service.security.system.SystemSecurityService; |
35 | -import org.thingsboard.server.utils.MiscUtils; | |
36 | 35 | |
37 | 36 | import javax.servlet.http.HttpServletRequest; |
38 | 37 | import javax.servlet.http.HttpServletResponse; | ... | ... |
... | ... | @@ -23,9 +23,9 @@ import org.springframework.security.core.Authentication; |
23 | 23 | import org.springframework.security.web.WebAttributes; |
24 | 24 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; |
25 | 25 | import org.springframework.stereotype.Component; |
26 | +import org.thingsboard.server.common.data.security.model.JwtToken; | |
26 | 27 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
27 | 28 | import org.thingsboard.server.service.security.model.SecurityUser; |
28 | -import org.thingsboard.server.service.security.model.token.JwtToken; | |
29 | 29 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
30 | 30 | |
31 | 31 | import javax.servlet.ServletException; | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | package org.thingsboard.server.service.security.exception; |
17 | 17 | |
18 | 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 | 21 | public class JwtExpiredTokenException extends AuthenticationException { |
22 | 22 | private static final long serialVersionUID = -5959543783324224864L; | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.model.token; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | 19 | import io.jsonwebtoken.Claims; |
20 | +import org.thingsboard.server.common.data.security.model.JwtToken; | |
20 | 21 | |
21 | 22 | public final class AccessJwtToken implements JwtToken { |
22 | 23 | private final String rawToken; | ... | ... |
... | ... | @@ -16,18 +16,26 @@ |
16 | 16 | package org.thingsboard.server.service.security.model.token; |
17 | 17 | |
18 | 18 | import io.jsonwebtoken.Claims; |
19 | +import io.jsonwebtoken.ExpiredJwtException; | |
19 | 20 | import io.jsonwebtoken.Jws; |
20 | 21 | import io.jsonwebtoken.Jwts; |
22 | +import io.jsonwebtoken.MalformedJwtException; | |
21 | 23 | import io.jsonwebtoken.SignatureAlgorithm; |
24 | +import io.jsonwebtoken.SignatureException; | |
25 | +import io.jsonwebtoken.UnsupportedJwtException; | |
26 | +import lombok.extern.slf4j.Slf4j; | |
22 | 27 | import org.apache.commons.lang3.StringUtils; |
23 | 28 | import org.springframework.beans.factory.annotation.Autowired; |
29 | +import org.springframework.security.authentication.BadCredentialsException; | |
24 | 30 | import org.springframework.security.core.GrantedAuthority; |
25 | 31 | import org.springframework.stereotype.Component; |
26 | 32 | import org.thingsboard.server.common.data.id.CustomerId; |
27 | 33 | import org.thingsboard.server.common.data.id.TenantId; |
28 | 34 | import org.thingsboard.server.common.data.id.UserId; |
29 | 35 | import org.thingsboard.server.common.data.security.Authority; |
36 | +import org.thingsboard.server.common.data.security.model.JwtToken; | |
30 | 37 | import org.thingsboard.server.config.JwtSettings; |
38 | +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; | |
31 | 39 | import org.thingsboard.server.service.security.model.SecurityUser; |
32 | 40 | import org.thingsboard.server.service.security.model.UserPrincipal; |
33 | 41 | |
... | ... | @@ -39,6 +47,7 @@ import java.util.UUID; |
39 | 47 | import java.util.stream.Collectors; |
40 | 48 | |
41 | 49 | @Component |
50 | +@Slf4j | |
42 | 51 | public class JwtTokenFactory { |
43 | 52 | |
44 | 53 | private static final String SCOPES = "scopes"; |
... | ... | @@ -97,7 +106,7 @@ public class JwtTokenFactory { |
97 | 106 | } |
98 | 107 | |
99 | 108 | public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) { |
100 | - Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); | |
109 | + Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken); | |
101 | 110 | Claims claims = jwsClaims.getBody(); |
102 | 111 | String subject = claims.getSubject(); |
103 | 112 | @SuppressWarnings("unchecked") |
... | ... | @@ -153,7 +162,7 @@ public class JwtTokenFactory { |
153 | 162 | } |
154 | 163 | |
155 | 164 | public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) { |
156 | - Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); | |
165 | + Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken); | |
157 | 166 | Claims claims = jwsClaims.getBody(); |
158 | 167 | String subject = claims.getSubject(); |
159 | 168 | @SuppressWarnings("unchecked") |
... | ... | @@ -171,4 +180,17 @@ public class JwtTokenFactory { |
171 | 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 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
... | ... | @@ -15,22 +15,10 @@ |
15 | 15 | */ |
16 | 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 | 20 | import java.io.Serializable; |
32 | 21 | |
33 | -@Slf4j | |
34 | 22 | public class RawAccessJwtToken implements JwtToken, Serializable { |
35 | 23 | |
36 | 24 | private static final long serialVersionUID = -797397445703066079L; |
... | ... | @@ -41,25 +29,6 @@ public class RawAccessJwtToken implements JwtToken, Serializable { |
41 | 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 | 32 | @Override |
64 | 33 | public String getToken() { |
65 | 34 | return token; | ... | ... |
application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
0 → 100644
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 | 27 | public static final String TENANT_PROFILE_CACHE = "tenantProfiles"; |
28 | 28 | public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; |
29 | 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 | 13 | * See the License for the specific language governing permissions and |
14 | 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 | 18 | import java.io.Serializable; |
19 | 19 | ... | ... |
... | ... | @@ -22,8 +22,8 @@ import com.google.common.util.concurrent.ListenableFuture; |
22 | 22 | import lombok.extern.slf4j.Slf4j; |
23 | 23 | import org.apache.commons.lang3.RandomStringUtils; |
24 | 24 | import org.apache.commons.lang3.StringUtils; |
25 | -import org.springframework.beans.factory.annotation.Autowired; | |
26 | 25 | import org.springframework.beans.factory.annotation.Value; |
26 | +import org.springframework.context.ApplicationEventPublisher; | |
27 | 27 | import org.springframework.context.annotation.Lazy; |
28 | 28 | import org.springframework.stereotype.Service; |
29 | 29 | import org.thingsboard.server.common.data.Customer; |
... | ... | @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData; |
38 | 38 | import org.thingsboard.server.common.data.page.PageLink; |
39 | 39 | import org.thingsboard.server.common.data.security.Authority; |
40 | 40 | import org.thingsboard.server.common.data.security.UserCredentials; |
41 | +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; | |
41 | 42 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
42 | 43 | import org.thingsboard.server.dao.customer.CustomerDao; |
43 | 44 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
... | ... | @@ -75,21 +76,26 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic |
75 | 76 | @Value("${security.user_login_case_sensitive:true}") |
76 | 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 | 100 | @Override |
95 | 101 | public User findUserByEmail(TenantId tenantId, String email) { |
... | ... | @@ -225,6 +231,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic |
225 | 231 | userCredentialsDao.removeById(tenantId, userCredentials.getUuidId()); |
226 | 232 | deleteEntityRelations(tenantId, userId); |
227 | 233 | userDao.removeById(tenantId, userId.getId()); |
234 | + eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId)); | |
228 | 235 | } |
229 | 236 | |
230 | 237 | @Override | ... | ... |
... | ... | @@ -149,8 +149,11 @@ export class AuthService { |
149 | 149 | } |
150 | 150 | |
151 | 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 | 159 | public activateByEmailCode(emailCode: string): Observable<LoginResponse> { | ... | ... |