From e993ec8aa483b31f073a1e0d64fb281886c65b16 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 2 May 2024 13:42:47 +0200 Subject: [PATCH] minor refactoring --- .../DefaultSystemDataLoaderService.java | 28 +++++++++-- .../settings/DefaultJwtSettingsService.java | 48 +++---------------- .../settings/DefaultJwtSettingsValidator.java | 4 +- .../auth/jwt/settings/JwtSettingsService.java | 2 - .../security/model/token/JwtTokenFactory.java | 7 +-- .../server/controller/AbstractWebTest.java | 4 -- 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index fbbc73ab82..fbfbd9e87b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -119,7 +119,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; -import static org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT; +import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault; +import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.validateTokenSigningKeyLength; @Service @Profile("install") @@ -155,6 +156,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Getter private boolean persistActivityToTelemetry; + @Value("${security.jwt.tokenExpirationTime:9000}") + private Integer tokenExpirationTime; + @Value("${security.jwt.refreshTokenExpTime:604800}") + private Integer refreshTokenExpTime; + @Value("${security.jwt.tokenIssuer:thingsboard.io}") + private String tokenIssuer; + @Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}") + private String tokenSigningKey; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -259,7 +269,17 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void createRandomJwtSettings() throws Exception { - jwtSettingsService.createRandomJwtSettings(); + if (jwtSettingsService.getJwtSettings() == null) { + log.info("Creating JWT admin settings..."); + var jwtSettings = new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey); + if (isSigningKeyDefault(jwtSettings) || !validateTokenSigningKeyLength(jwtSettings)) { + jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString( + RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8))); + } + jwtSettingsService.saveJwtSettings(jwtSettings); + } else { + log.info("Skip creating JWT admin settings because they already exist."); + } } @Override @@ -268,13 +288,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { boolean invalidSignKey = false; - if (TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) { + if (isSigningKeyDefault(jwtSettings)) { log.warn("WARNING: The platform is configured to use default JWT Signing Key. " + "Added new temporary JWT Signing Key. " + "This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " + "Navigate to \"System settings -> Security settings\" while logged in as a System Administrator."); invalidSignKey = true; - } else if (Base64.getDecoder().decode(jwtSettings.getTokenSigningKey()).length * Byte.SIZE < 512) { + } else if (!validateTokenSigningKeyLength(jwtSettings)) { log.warn("WARNING: The platform is configured to use JWT Signing Key with length less then 512 bits of data. " + "Added new temporary JWT Signing Key. " + "This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " + diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java index cfb540fbf1..92019341c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.service.security.auth.jwt.settings; +import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; @@ -28,7 +27,6 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.dao.settings.AdminSettingsService; -import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Objects; import java.util.Optional; @@ -42,35 +40,8 @@ public class DefaultJwtSettingsService implements JwtSettingsService { private final Optional tbClusterService; private final JwtSettingsValidator jwtSettingsValidator; - @Value("${security.jwt.tokenExpirationTime:9000}") - private Integer tokenExpirationTime; - @Value("${security.jwt.refreshTokenExpTime:604800}") - private Integer refreshTokenExpTime; - @Value("${security.jwt.tokenIssuer:thingsboard.io}") - private String tokenIssuer; - @Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}") - private String tokenSigningKey; - private volatile JwtSettings jwtSettings = null; //lazy init - /** - * Create JWT admin settings is intended to be called from Install scripts only - */ - @Override - public void createRandomJwtSettings() { - if (getJwtSettingsFromDb() == null) { - log.info("Creating JWT admin settings..."); - this.jwtSettings = getJwtSettingsFromYml(); - if (isSigningKeyDefault(jwtSettings) || Base64.getDecoder().decode(jwtSettings.getTokenSigningKey()).length * Byte.SIZE < 512) { - this.jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString( - RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8))); - } - saveJwtSettings(jwtSettings); - } else { - log.info("Skip creating JWT admin settings because they already exist."); - } - } - @Override public JwtSettings saveJwtSettings(JwtSettings jwtSettings) { jwtSettingsValidator.validate(jwtSettings); @@ -103,22 +74,13 @@ public class DefaultJwtSettingsService implements JwtSettingsService { if (this.jwtSettings == null || forceReload) { synchronized (this) { if (this.jwtSettings == null || forceReload) { - JwtSettings result = getJwtSettingsFromDb(); - if (result == null) { - result = getJwtSettingsFromYml(); - log.warn("Loading the JWT settings from YML since there are no settings in DB. Looks like the upgrade script was not applied."); - } - this.jwtSettings = result; + jwtSettings = getJwtSettingsFromDb(); } } } return this.jwtSettings; } - private JwtSettings getJwtSettingsFromYml() { - return new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey); - } - private JwtSettings getJwtSettingsFromDb() { AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); return adminJwtSettings != null ? mapAdminToJwtSettings(adminJwtSettings) : null; @@ -138,8 +100,12 @@ public class DefaultJwtSettingsService implements JwtSettingsService { return adminJwtSettings; } - private boolean isSigningKeyDefault(JwtSettings settings) { + public static boolean isSigningKeyDefault(JwtSettings settings) { return TOKEN_SIGNING_KEY_DEFAULT.equals(settings.getTokenSigningKey()); } + public static boolean validateTokenSigningKeyLength(JwtSettings settings) { + return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= Jwts.SIG.HS512.getKeyBitLength(); + } + } 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 4ca02a1add..1de9e2c0c2 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 @@ -27,6 +27,8 @@ import java.util.Base64; import java.util.Optional; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault; + @Component @RequiredArgsConstructor public class DefaultJwtSettingsValidator implements JwtSettingsValidator { @@ -59,7 +61,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 && !JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) { + if (decodedKey.length * Byte.SIZE < 512 && !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/auth/jwt/settings/JwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java index 12b1d017c2..d3aa261b00 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java @@ -26,8 +26,6 @@ public interface JwtSettingsService { JwtSettings reloadJwtSettings(); - void createRandomJwtSettings(); - JwtSettings saveJwtSettings(JwtSettings jwtSettings); } 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 abdcf76888..6be020b1c5 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 @@ -187,9 +187,10 @@ public class JwtTokenFactory { UserPrincipal principal = securityUser.getUserPrincipal(); - ClaimsBuilder claimsBuilder = Jwts.claims().subject(principal.getValue()); - claimsBuilder.add(USER_ID, securityUser.getId().getId().toString()); - claimsBuilder.add(SCOPES, scopes); + ClaimsBuilder claimsBuilder = Jwts.claims() + .subject(principal.getValue()) + .add(USER_ID, securityUser.getId().getId().toString()) + .add(SCOPES, scopes); if (securityUser.getSessionId() != null) { claimsBuilder.add(SESSION_ID, securityUser.getSessionId()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index ffc37dc7a1..b83a1b567c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -21,10 +21,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Header; import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwt; -import io.jsonwebtoken.Jwts; import lombok.extern.slf4j.Slf4j; import org.awaitility.Awaitility; import org.hamcrest.Matcher; @@ -124,7 +121,6 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import javax.crypto.SecretKey; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle;