diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index cdb3f19601..445d1a6bba 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -94,6 +94,7 @@ import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.resource.TbImageService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; +import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; @@ -172,10 +173,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; protected final JwtSettingsService jwtSettingsService; + protected final JwtTokenFactory jwtTokenFactory; public void init(String nfConsumerThreadName) { this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName)); @@ -163,6 +166,7 @@ public abstract class AbstractConsumerService= Jwts.SIG.HS512.getKeyBitLength(); + return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= KEY_LENGTH; } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java index 1de9e2c0c2..ea8c67e3dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault; +import static org.thingsboard.server.service.security.model.token.JwtTokenFactory.KEY_LENGTH; @Component @RequiredArgsConstructor @@ -61,7 +62,7 @@ public class DefaultJwtSettingsValidator implements JwtSettingsValidator { if (Arrays.isNullOrEmpty(decodedKey)) { throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!"); } - if (decodedKey.length * Byte.SIZE < 512 && !isSigningKeyDefault(jwtSettings)) { + if (decodedKey.length * Byte.SIZE < KEY_LENGTH && !isSigningKeyDefault(jwtSettings)) { throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 512 bits of data!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index 6be020b1c5..9542a9de2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -20,6 +20,7 @@ import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureException; @@ -57,6 +58,8 @@ import java.util.stream.Collectors; @Slf4j public class JwtTokenFactory { + public static int KEY_LENGTH = Jwts.SIG.HS512.getKeyBitLength(); + private static final String SCOPES = "scopes"; private static final String USER_ID = "userId"; private static final String FIRST_NAME = "firstName"; @@ -69,6 +72,9 @@ public class JwtTokenFactory { private final JwtSettingsService jwtSettingsService; + private volatile JwtParser jwtParser; + private volatile SecretKey secretKey; + /** * Factory method for issuing new JWT Tokens. */ @@ -180,6 +186,11 @@ public class JwtTokenFactory { return new AccessJwtToken(jwtBuilder.compact()); } + public void reload() { + getSecretKey(true); + getJwtParser(true); + } + private JwtBuilder setUpToken(SecurityUser securityUser, List scopes, long expirationTime) { if (StringUtils.isBlank(securityUser.getEmail())) { throw new IllegalArgumentException("Cannot create JWT Token without username/email"); @@ -202,15 +213,12 @@ public class JwtTokenFactory { .issuer(jwtSettingsService.getJwtSettings().getTokenIssuer()) .issuedAt(Date.from(currentTime.toInstant())) .expiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant())) - .signWith(toSecretKey(jwtSettingsService.getJwtSettings().getTokenSigningKey()), Jwts.SIG.HS512); + .signWith(getSecretKey(false), Jwts.SIG.HS512); } public Jws parseTokenClaims(String token) { try { - return Jwts.parser() - .verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey()))) - .build() - .parseSignedClaims(token); + return getJwtParser(false).parseSignedClaims(token); } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) { log.debug("Invalid JWT Token", ex); throw new BadCredentialsException("Invalid JWT token: ", ex); @@ -226,9 +234,28 @@ public class JwtTokenFactory { return new JwtPair(accessToken.getToken(), refreshToken.getToken()); } - private SecretKey toSecretKey(String base64Key) { - byte[] decodedToken = Base64.getDecoder().decode(base64Key); - return new SecretKeySpec(decodedToken, "HmacSHA512"); + private SecretKey getSecretKey(boolean forceReload) { + if (secretKey == null || forceReload) { + synchronized (this) { + if (secretKey == null || forceReload) { + byte[] decodedToken = Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey()); + secretKey = new SecretKeySpec(decodedToken, "HmacSHA512"); + } + } + } + return secretKey; } + private JwtParser getJwtParser(boolean forceReload) { + if (jwtParser == null || forceReload) { + synchronized (this) { + if (jwtParser == null || forceReload) { + jwtParser = Jwts.parser() + .verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey()))) + .build(); + } + } + } + return jwtParser; + } }