Browse Source

[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
pull/4277/head
Viacheslav Klimov 6 years ago
committed by GitHub
parent
commit
bc6efa5e1e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      application/src/main/java/org/thingsboard/server/config/JwtSettings.java
  2. 61
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  3. 28
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  4. 85
      application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
  5. 17
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
  6. 21
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
  11. 1
      application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java
  12. 26
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  13. 33
      application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
  14. 3
      application/src/main/resources/thingsboard.yml
  15. 184
      application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
  16. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  17. 30
      common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java
  18. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtToken.java
  19. 39
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  20. 7
      ui-ngx/src/app/core/auth/auth.service.ts

2
application/src/main/java/org/thingsboard/server/config/JwtSettings.java

@ -17,7 +17,7 @@ package org.thingsboard.server.config;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.common.data.security.model.JwtToken;
@Configuration @Configuration
@ConfigurationProperties(prefix = "security.jwt") @ConfigurationProperties(prefix = "security.jwt")

61
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -18,8 +18,9 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
@ -39,48 +41,37 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import ua_parser.Client; import ua_parser.Client;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.List;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@Slf4j @Slf4j
@RequiredArgsConstructor
public class AuthController extends BaseController { public class AuthController extends BaseController {
private final BCryptPasswordEncoder passwordEncoder;
@Autowired private final JwtTokenFactory tokenFactory;
private BCryptPasswordEncoder passwordEncoder; private final RefreshTokenRepository refreshTokenRepository;
private final MailService mailService;
@Autowired private final SystemSecurityService systemSecurityService;
private JwtTokenFactory tokenFactory; private final AuditLogService auditLogService;
private final ApplicationEventPublisher eventPublisher;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@Autowired
private MailService mailService;
@Autowired
private SystemSecurityService systemSecurityService;
@Autowired
private AuditLogService auditLogService;
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/user", method = RequestMethod.GET) @RequestMapping(value = "/auth/user", method = RequestMethod.GET)
@ -103,8 +94,7 @@ public class AuthController extends BaseController {
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void changePassword ( public ObjectNode changePassword(@RequestBody JsonNode changePasswordRequest) throws ThingsboardException {
@RequestBody JsonNode changePasswordRequest) throws ThingsboardException {
try { try {
String currentPassword = changePasswordRequest.get("currentPassword").asText(); String currentPassword = changePasswordRequest.get("currentPassword").asText();
String newPassword = changePasswordRequest.get("newPassword").asText(); String newPassword = changePasswordRequest.get("newPassword").asText();
@ -119,6 +109,12 @@ public class AuthController extends BaseController {
} }
userCredentials.setPassword(passwordEncoder.encode(newPassword)); userCredentials.setPassword(passwordEncoder.encode(newPassword));
userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials);
eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
ObjectNode response = JacksonUtil.newObjectNode();
response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken());
response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken());
return response;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -135,7 +131,7 @@ public class AuthController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
@RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET)
public ResponseEntity<String> checkActivateToken( public ResponseEntity<String> checkActivateToken(
@RequestParam(value = "activateToken") String activateToken) { @RequestParam(value = "activateToken") String activateToken) {
@ -157,7 +153,7 @@ public class AuthController extends BaseController {
} }
return new ResponseEntity<>(headers, responseStatus); return new ResponseEntity<>(headers, responseStatus);
} }
@RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void requestResetPasswordByEmail ( public void requestResetPasswordByEmail (
@ -170,13 +166,13 @@ public class AuthController extends BaseController {
String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
userCredentials.getResetToken()); userCredentials.getResetToken());
mailService.sendResetPasswordEmail(resetUrl, email); mailService.sendResetPasswordEmail(resetUrl, email);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
} }
@RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET)
public ResponseEntity<String> checkResetToken( public ResponseEntity<String> checkResetToken(
@RequestParam(value = "resetToken") String resetToken) { @RequestParam(value = "resetToken") String resetToken) {
@ -198,7 +194,7 @@ public class AuthController extends BaseController {
} }
return new ResponseEntity<>(headers, responseStatus); return new ResponseEntity<>(headers, responseStatus);
} }
@RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ResponseBody @ResponseBody
@ -240,7 +236,7 @@ public class AuthController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
@RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ResponseBody @ResponseBody
@ -268,6 +264,7 @@ public class AuthController extends BaseController {
String email = user.getEmail(); String email = user.getEmail();
mailService.sendPasswordWasResetEmail(loginUrl, email); mailService.sendPasswordWasResetEmail(loginUrl, email);
eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);

28
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter; import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -44,11 +45,12 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -56,6 +58,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@RequiredArgsConstructor
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@ -69,18 +72,11 @@ public class UserController extends BaseController {
@Getter @Getter
private boolean userTokenAccessEnabled; private boolean userTokenAccessEnabled;
@Autowired private final MailService mailService;
private MailService mailService; private final JwtTokenFactory tokenFactory;
private final RefreshTokenRepository refreshTokenRepository;
@Autowired private final SystemSecurityService systemSecurityService;
private JwtTokenFactory tokenFactory; private final ApplicationEventPublisher eventPublisher;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@Autowired
private SystemSecurityService systemSecurityService;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
@ -341,6 +337,10 @@ public class UserController extends BaseController {
User user = checkUserId(userId, Operation.WRITE); User user = checkUserId(userId, Operation.WRITE);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled);
if (!userCredentialsEnabled) {
eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

85
application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.auth;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import javax.annotation.PostConstruct;
import java.util.Optional;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@Service
@RequiredArgsConstructor
public class TokenOutdatingService {
private final CacheManager cacheManager;
private final JwtTokenFactory tokenFactory;
private final JwtSettings jwtSettings;
private Cache tokenOutdatageTimeCache;
@PostConstruct
protected void initCache() {
tokenOutdatageTimeCache = cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE);
}
@EventListener(classes = UserAuthDataChangedEvent.class)
public void onUserAuthDataChanged(UserAuthDataChangedEvent userAuthDataChangedEvent) {
outdateOldUserTokens(userAuthDataChangedEvent.getUserId());
}
public boolean isOutdated(JwtToken token, UserId userId) {
Claims claims = tokenFactory.parseTokenClaims(token).getBody();
long issueTime = claims.getIssuedAt().getTime();
return Optional.ofNullable(tokenOutdatageTimeCache.get(toKey(userId), Long.class))
.map(outdatageTime -> {
if (System.currentTimeMillis() - outdatageTime <= SECONDS.toMillis(jwtSettings.getRefreshTokenExpTime())) {
return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime);
} else {
/*
* Means that since the outdating has passed more than
* the lifetime of refresh token (the longest lived)
* and there is no need to store outdatage time anymore
* as all the tokens issued before the outdatage time
* are now expired by themselves
* */
tokenOutdatageTimeCache.evict(toKey(userId));
return false;
}
})
.orElse(false);
}
public void outdateOldUserTokens(UserId userId) {
tokenOutdatageTimeCache.put(toKey(userId), System.currentTimeMillis());
}
private String toKey(UserId userId) {
return userId.getId().toString();
}
}

17
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java

@ -15,31 +15,34 @@
*/ */
package org.thingsboard.server.service.security.auth.jwt; package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.service.security.auth.JwtAuthenticationToken; import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
@Component @Component
@SuppressWarnings("unchecked") @RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider { public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory; private final JwtTokenFactory tokenFactory;
private final TokenOutdatingService tokenOutdatingService;
@Autowired
public JwtAuthenticationProvider(JwtTokenFactory tokenFactory) {
this.tokenFactory = tokenFactory;
}
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken); SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new JwtExpiredTokenException("Token is outdated");
}
return new JwtAuthenticationToken(securityUser); return new JwtAuthenticationToken(securityUser);
} }

21
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -15,9 +15,10 @@
*/ */
package org.thingsboard.server.service.security.auth.jwt; package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -44,18 +46,12 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.UUID; import java.util.UUID;
@Component @Component
@RequiredArgsConstructor
public class RefreshTokenAuthenticationProvider implements AuthenticationProvider { public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory; private final JwtTokenFactory tokenFactory;
private final UserService userService; private final UserService userService;
private final CustomerService customerService; private final CustomerService customerService;
private final TokenOutdatingService tokenOutdatingService;
@Autowired
public RefreshTokenAuthenticationProvider(final UserService userService, final CustomerService customerService, final JwtTokenFactory tokenFactory) {
this.userService = userService;
this.customerService = customerService;
this.tokenFactory = tokenFactory;
}
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
@ -63,12 +59,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken); SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
UserPrincipal principal = unsafeUser.getUserPrincipal(); UserPrincipal principal = unsafeUser.getUserPrincipal();
SecurityUser securityUser; SecurityUser securityUser;
if (principal.getType() == UserPrincipal.Type.USER_NAME) { if (principal.getType() == UserPrincipal.Type.USER_NAME) {
securityUser = authenticateByUserId(unsafeUser.getId()); securityUser = authenticateByUserId(unsafeUser.getId());
} else { } else {
securityUser = authenticateByPublicId(principal.getValue()); securityUser = authenticateByPublicId(principal.getValue());
} }
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new CredentialsExpiredException("Token is outdated");
}
return new RefreshAuthenticationToken(securityUser); return new RefreshAuthenticationToken(securityUser);
} }
@ -91,7 +93,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned"); if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal); SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
return securityUser; return securityUser;

2
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java

@ -17,8 +17,8 @@ package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@Component @Component

3
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -26,13 +26,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java

@ -23,9 +23,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes; import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import javax.servlet.ServletException; import javax.servlet.ServletException;

2
application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.security.exception; package org.thingsboard.server.service.security.exception;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.common.data.security.model.JwtToken;
public class JwtExpiredTokenException extends AuthenticationException { public class JwtExpiredTokenException extends AuthenticationException {
private static final long serialVersionUID = -5959543783324224864L; private static final long serialVersionUID = -5959543783324224864L;

1
application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.model.token;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import org.thingsboard.server.common.data.security.model.JwtToken;
public final class AccessJwtToken implements JwtToken { public final class AccessJwtToken implements JwtToken {
private final String rawToken; private final String rawToken;

26
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -16,18 +16,26 @@
package org.thingsboard.server.service.security.model.token; package org.thingsboard.server.service.security.model.token;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings; import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
@ -39,6 +47,7 @@ import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Component @Component
@Slf4j
public class JwtTokenFactory { public class JwtTokenFactory {
private static final String SCOPES = "scopes"; private static final String SCOPES = "scopes";
@ -97,7 +106,7 @@ public class JwtTokenFactory {
} }
public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) { public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) {
Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
Claims claims = jwsClaims.getBody(); Claims claims = jwsClaims.getBody();
String subject = claims.getSubject(); String subject = claims.getSubject();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -153,7 +162,7 @@ public class JwtTokenFactory {
} }
public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) { public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) {
Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
Claims claims = jwsClaims.getBody(); Claims claims = jwsClaims.getBody();
String subject = claims.getSubject(); String subject = claims.getSubject();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -171,4 +180,17 @@ public class JwtTokenFactory {
return securityUser; return securityUser;
} }
public Jws<Claims> parseTokenClaims(JwtToken token) {
try {
return Jwts.parser()
.setSigningKey(settings.getTokenSigningKey())
.parseClaimsJws(token.getToken());
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx);
}
}
} }

33
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java

@ -15,22 +15,10 @@
*/ */
package org.thingsboard.server.service.security.model.token; package org.thingsboard.server.service.security.model.token;
import io.jsonwebtoken.Claims; import org.thingsboard.server.common.data.security.model.JwtToken;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import java.io.Serializable; import java.io.Serializable;
@Slf4j
public class RawAccessJwtToken implements JwtToken, Serializable { public class RawAccessJwtToken implements JwtToken, Serializable {
private static final long serialVersionUID = -797397445703066079L; private static final long serialVersionUID = -797397445703066079L;
@ -41,25 +29,6 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
this.token = token; this.token = token;
} }
/**
* Parses and validates JWT Token signature.
*
* @throws BadCredentialsException
* @throws JwtExpiredTokenException
*
*/
public Jws<Claims> parseClaims(String signingKey) {
try {
return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
}
}
@Override @Override
public String getToken() { public String getToken() {
return token; return token;

3
application/src/main/resources/thingsboard.yml

@ -362,6 +362,9 @@ caffeine:
attributes: attributes:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 100000 maxSize: 100000
tokensOutdatageTime:
timeToLiveInMinutes: 20000
maxSize: 10000
redis: redis:
# standalone or cluster # standalone or cluster

184
application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java

@ -0,0 +1,184 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.auth;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TokenOutdatingTest {
private JwtAuthenticationProvider accessTokenAuthenticationProvider;
private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
private TokenOutdatingService tokenOutdatingService;
private ConcurrentMapCacheManager cacheManager;
private JwtTokenFactory tokenFactory;
private JwtSettings jwtSettings;
private UserId userId;
@BeforeEach
public void setUp() {
jwtSettings = new JwtSettings();
jwtSettings.setTokenIssuer("test.io");
jwtSettings.setTokenExpirationTime((int) MINUTES.toSeconds(10));
jwtSettings.setRefreshTokenExpTime((int) DAYS.toSeconds(7));
jwtSettings.setTokenSigningKey("secret");
tokenFactory = new JwtTokenFactory(jwtSettings);
cacheManager = new ConcurrentMapCacheManager();
tokenOutdatingService = new TokenOutdatingService(cacheManager, tokenFactory, jwtSettings);
tokenOutdatingService.initCache();
userId = new UserId(UUID.randomUUID());
UserService userService = mock(UserService.class);
User user = new User();
user.setId(userId);
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("email");
when(userService.findUserById(any(), eq(userId))).thenReturn(user);
UserCredentials userCredentials = new UserCredentials();
userCredentials.setEnabled(true);
when(userService.findUserCredentialsByUserId(any(), eq(userId))).thenReturn(userCredentials);
accessTokenAuthenticationProvider = new JwtAuthenticationProvider(tokenFactory, tokenOutdatingService);
refreshTokenAuthenticationProvider = new RefreshTokenAuthenticationProvider(tokenFactory, userService, mock(CustomerService.class), tokenOutdatingService);
}
@Test
public void testOutdateOldUserTokens() throws Exception {
JwtToken jwtToken = createAccessJwtToken(userId);
SECONDS.sleep(1); // need to wait before outdating so that outdatage time is strictly after token issue time
tokenOutdatingService.outdateOldUserTokens(userId);
assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
SECONDS.sleep(1);
JwtToken newJwtToken = tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
assertFalse(tokenOutdatingService.isOutdated(newJwtToken, userId));
}
@Test
public void testAuthenticateWithOutdatedAccessToken() throws InterruptedException {
RawAccessJwtToken accessJwtToken = getRawJwtToken(createAccessJwtToken(userId));
assertDoesNotThrow(() -> {
accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
});
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
assertThrows(JwtExpiredTokenException.class, () -> {
accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
});
}
@Test
public void testAuthenticateWithOutdatedRefreshToken() throws InterruptedException {
RawAccessJwtToken refreshJwtToken = getRawJwtToken(createRefreshJwtToken(userId));
assertDoesNotThrow(() -> {
refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
});
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
assertThrows(CredentialsExpiredException.class, () -> {
refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
});
}
@Test
public void testTokensOutdatageTimeRemovalFromCache() throws Exception {
JwtToken jwtToken = createAccessJwtToken(userId);
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
int refreshTokenExpirationTime = 5;
jwtSettings.setRefreshTokenExpTime(refreshTokenExpirationTime);
SECONDS.sleep(refreshTokenExpirationTime - 2);
assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
assertNotNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
SECONDS.sleep(3);
assertFalse(tokenOutdatingService.isOutdated(jwtToken, userId));
assertNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
}
private JwtToken createAccessJwtToken(UserId userId) {
return tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
}
private JwtToken createRefreshJwtToken(UserId userId) {
return tokenFactory.createRefreshToken(createMockSecurityUser(userId));
}
private RawAccessJwtToken getRawJwtToken(JwtToken token) {
return new RawAccessJwtToken(token.getToken());
}
private SecurityUser createMockSecurityUser(UserId userId) {
SecurityUser securityUser = new SecurityUser();
securityUser.setEmail("email");
securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail()));
securityUser.setAuthority(Authority.CUSTOMER_USER);
securityUser.setId(userId);
return securityUser;
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -27,4 +27,5 @@ public class CacheConstants {
public static final String TENANT_PROFILE_CACHE = "tenantProfiles"; public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ATTRIBUTES_CACHE = "attributes"; public static final String ATTRIBUTES_CACHE = "attributes";
public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
} }

30
common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.security.event;
import org.thingsboard.server.common.data.id.UserId;
public class UserAuthDataChangedEvent {
private final UserId userId;
public UserAuthDataChangedEvent(UserId userId) {
this.userId = userId;
}
public UserId getUserId() {
return userId;
}
}

2
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java → common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtToken.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.security.model.token; package org.thingsboard.server.common.data.security.model;
import java.io.Serializable; import java.io.Serializable;

39
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -22,8 +22,8 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -75,21 +76,26 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
@Value("${security.user_login_case_sensitive:true}") @Value("${security.user_login_case_sensitive:true}")
private boolean userLoginCaseSensitive; private boolean userLoginCaseSensitive;
@Autowired private final UserDao userDao;
private UserDao userDao; private final UserCredentialsDao userCredentialsDao;
private final TenantDao tenantDao;
@Autowired private final CustomerDao customerDao;
private UserCredentialsDao userCredentialsDao; private final TbTenantProfileCache tenantProfileCache;
private final ApplicationEventPublisher eventPublisher;
@Autowired
private TenantDao tenantDao; public UserServiceImpl(UserDao userDao,
UserCredentialsDao userCredentialsDao,
@Autowired TenantDao tenantDao,
private CustomerDao customerDao; CustomerDao customerDao,
@Lazy TbTenantProfileCache tenantProfileCache,
@Autowired ApplicationEventPublisher eventPublisher) {
@Lazy this.userDao = userDao;
private TbTenantProfileCache tenantProfileCache; this.userCredentialsDao = userCredentialsDao;
this.tenantDao = tenantDao;
this.customerDao = customerDao;
this.tenantProfileCache = tenantProfileCache;
this.eventPublisher = eventPublisher;
}
@Override @Override
public User findUserByEmail(TenantId tenantId, String email) { public User findUserByEmail(TenantId tenantId, String email) {
@ -225,6 +231,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
userCredentialsDao.removeById(tenantId, userCredentials.getUuidId()); userCredentialsDao.removeById(tenantId, userCredentials.getUuidId());
deleteEntityRelations(tenantId, userId); deleteEntityRelations(tenantId, userId);
userDao.removeById(tenantId, userId.getId()); userDao.removeById(tenantId, userId.getId());
eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
} }
@Override @Override

7
ui-ngx/src/app/core/auth/auth.service.ts

@ -149,8 +149,11 @@ export class AuthService {
} }
public changePassword(currentPassword: string, newPassword: string) { public changePassword(currentPassword: string, newPassword: string) {
return this.http.post('/api/auth/changePassword', return this.http.post('/api/auth/changePassword', {currentPassword, newPassword}, defaultHttpOptions()).pipe(
{currentPassword, newPassword}, defaultHttpOptions()); tap((loginResponse: LoginResponse) => {
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
}
));
} }
public activateByEmailCode(emailCode: string): Observable<LoginResponse> { public activateByEmailCode(emailCode: string): Observable<LoginResponse> {

Loading…
Cancel
Save