Browse Source

refactored creationing Jwt SecretKey and JwtParcer

pull/10671/head
YevhenBondarenko 2 years ago
parent
commit
1f648806f3
  1. 5
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  2. 7
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  3. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  4. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java
  6. 43
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

5
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.resource.TbImageService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; 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.state.DeviceStateService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
@ -172,10 +173,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
JwtSettingsService jwtSettingsService, JwtSettingsService jwtSettingsService,
JwtTokenFactory jwtTokenFactory,
NotificationSchedulerService notificationSchedulerService, NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor, NotificationRuleProcessor notificationRuleProcessor,
TbImageService imageService) { TbImageService imageService) {
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService,
eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService, jwtTokenFactory);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

7
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -56,6 +56,8 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -86,9 +88,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbApiUsageStateService apiUsageStateService, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
JwtSettingsService jwtSettingsService) { JwtSettingsService jwtSettingsService,
JwtTokenFactory jwtTokenFactory) {
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService,
eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), jwtSettingsService); eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), jwtSettingsService, jwtTokenFactory);
this.ctx = ctx; this.ctx = ctx;
this.tbDeviceRpcService = tbDeviceRpcService; this.tbDeviceRpcService = tbDeviceRpcService;
this.queueService = queueService; this.queueService = queueService;

4
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -48,6 +48,8 @@ import org.thingsboard.server.service.queue.TbPackProcessingContext;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -75,6 +77,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final JwtSettingsService jwtSettingsService; protected final JwtSettingsService jwtSettingsService;
protected final JwtTokenFactory jwtTokenFactory;
public void init(String nfConsumerThreadName) { public void init(String nfConsumerThreadName) {
this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName)); this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName));
@ -163,6 +166,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (TenantId.SYS_TENANT_ID.equals(tenantId)) { if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
jwtSettingsService.reloadJwtSettings(); jwtSettingsService.reloadJwtSettings();
jwtTokenFactory.reload();
return; return;
} else { } else {
tenantProfileCache.evict(tenantId); tenantProfileCache.evict(tenantId);

5
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.security.auth.jwt.settings; package org.thingsboard.server.service.security.auth.jwt.settings;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,6 +30,8 @@ import java.util.Base64;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static org.thingsboard.server.service.security.model.token.JwtTokenFactory.KEY_LENGTH;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@ -105,7 +106,7 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
} }
public static boolean validateTokenSigningKeyLength(JwtSettings settings) { public static boolean validateTokenSigningKeyLength(JwtSettings settings) {
return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= Jwts.SIG.HS512.getKeyBitLength(); return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= KEY_LENGTH;
} }
} }

3
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 java.util.concurrent.TimeUnit;
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault; 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 @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -61,7 +62,7 @@ public class DefaultJwtSettingsValidator implements JwtSettingsValidator {
if (Arrays.isNullOrEmpty(decodedKey)) { if (Arrays.isNullOrEmpty(decodedKey)) {
throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!"); 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!"); throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 512 bits of data!");
} }

43
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.ExpiredJwtException;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.SignatureException;
@ -57,6 +58,8 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class JwtTokenFactory { public class JwtTokenFactory {
public static int KEY_LENGTH = Jwts.SIG.HS512.getKeyBitLength();
private static final String SCOPES = "scopes"; private static final String SCOPES = "scopes";
private static final String USER_ID = "userId"; private static final String USER_ID = "userId";
private static final String FIRST_NAME = "firstName"; private static final String FIRST_NAME = "firstName";
@ -69,6 +72,9 @@ public class JwtTokenFactory {
private final JwtSettingsService jwtSettingsService; private final JwtSettingsService jwtSettingsService;
private volatile JwtParser jwtParser;
private volatile SecretKey secretKey;
/** /**
* Factory method for issuing new JWT Tokens. * Factory method for issuing new JWT Tokens.
*/ */
@ -180,6 +186,11 @@ public class JwtTokenFactory {
return new AccessJwtToken(jwtBuilder.compact()); return new AccessJwtToken(jwtBuilder.compact());
} }
public void reload() {
getSecretKey(true);
getJwtParser(true);
}
private JwtBuilder setUpToken(SecurityUser securityUser, List<String> scopes, long expirationTime) { private JwtBuilder setUpToken(SecurityUser securityUser, List<String> scopes, long expirationTime) {
if (StringUtils.isBlank(securityUser.getEmail())) { if (StringUtils.isBlank(securityUser.getEmail())) {
throw new IllegalArgumentException("Cannot create JWT Token without username/email"); throw new IllegalArgumentException("Cannot create JWT Token without username/email");
@ -202,15 +213,12 @@ public class JwtTokenFactory {
.issuer(jwtSettingsService.getJwtSettings().getTokenIssuer()) .issuer(jwtSettingsService.getJwtSettings().getTokenIssuer())
.issuedAt(Date.from(currentTime.toInstant())) .issuedAt(Date.from(currentTime.toInstant()))
.expiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant())) .expiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant()))
.signWith(toSecretKey(jwtSettingsService.getJwtSettings().getTokenSigningKey()), Jwts.SIG.HS512); .signWith(getSecretKey(false), Jwts.SIG.HS512);
} }
public Jws<Claims> parseTokenClaims(String token) { public Jws<Claims> parseTokenClaims(String token) {
try { try {
return Jwts.parser() return getJwtParser(false).parseSignedClaims(token);
.verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey())))
.build()
.parseSignedClaims(token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) { } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) {
log.debug("Invalid JWT Token", ex); log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("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()); return new JwtPair(accessToken.getToken(), refreshToken.getToken());
} }
private SecretKey toSecretKey(String base64Key) { private SecretKey getSecretKey(boolean forceReload) {
byte[] decodedToken = Base64.getDecoder().decode(base64Key); if (secretKey == null || forceReload) {
return new SecretKeySpec(decodedToken, "HmacSHA512"); 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;
}
} }

Loading…
Cancel
Save