JwtSettingsService workout: Lazy and Optional clusterService, correctness on first Install and upgrade, reload JWT on cluster notification, update jwt settings using existing id

This commit is contained in:
Sergey Matvienko 2022-11-09 23:28:34 +01:00
parent 7186632e5a
commit 1a9b8a1ebe
6 changed files with 63 additions and 28 deletions

View File

@ -19,6 +19,8 @@ public interface JwtSettingsService {
JwtSettings getJwtSettings(); JwtSettings getJwtSettings();
void reloadJwtSettings();
void createJwtAdminSettings(); void createJwtAdminSettings();
JwtSettings saveJwtSettings(JwtSettings jwtSettings); JwtSettings saveJwtSettings(JwtSettings jwtSettings);

View File

@ -19,7 +19,10 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
@ -33,6 +36,7 @@ import javax.validation.ValidationException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -42,24 +46,37 @@ public class JwtSettingsServiceDefault implements JwtSettingsService {
static final String ADMIN_SETTINGS_JWT_KEY = "jwt"; static final String ADMIN_SETTINGS_JWT_KEY = "jwt";
static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey"; static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
static final String TB_ALLOW_DEFAULT_JWT_SIGNING_KEY = "TB_ALLOW_DEFAULT_JWT_SIGNING_KEY"; static final String TB_ALLOW_DEFAULT_JWT_SIGNING_KEY = "TB_ALLOW_DEFAULT_JWT_SIGNING_KEY";
@Lazy
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
private final TbClusterService tbClusterService; @Lazy
private final Optional<TbClusterService> tbClusterService;
private final JwtSettingsValidator jwtSettingsValidator; private final JwtSettingsValidator jwtSettingsValidator;
private final Environment environment;
@Getter @Getter
private final JwtSettings jwtSettings; private final JwtSettings jwtSettings;
@Value("${install.upgrade:false}")
private boolean isUpgrade;
@PostConstruct @PostConstruct
public void init() { public void init() {
if (!isFirstInstall()) {
reloadJwtSettings(); reloadJwtSettings();
} }
}
void reloadJwtSettings() { private boolean isInstall() {
return environment.acceptsProfiles(Profiles.of("install"));
}
private boolean isFirstInstall() {
return isInstall() && !isUpgrade;
}
@Override
public void reloadJwtSettings() {
AdminSettings adminJwtSettings = findJwtAdminSettings(); AdminSettings adminJwtSettings = findJwtAdminSettings();
if (adminJwtSettings != null) { if (adminJwtSettings != null) {
log.debug("Loading the JWT admin settings from database"); log.info("Reloading the JWT admin settings from database");
JwtSettings jwtLoaded = mapAdminToJwtSettings(adminJwtSettings); JwtSettings jwtLoaded = mapAdminToJwtSettings(adminJwtSettings);
jwtSettings.setRefreshTokenExpTime(jwtLoaded.getRefreshTokenExpTime()); jwtSettings.setRefreshTokenExpTime(jwtLoaded.getRefreshTokenExpTime());
jwtSettings.setTokenExpirationTime(jwtLoaded.getTokenExpirationTime()); jwtSettings.setTokenExpirationTime(jwtLoaded.getTokenExpirationTime());
@ -67,7 +84,7 @@ public class JwtSettingsServiceDefault implements JwtSettingsService {
jwtSettings.setTokenSigningKey(jwtLoaded.getTokenSigningKey()); jwtSettings.setTokenSigningKey(jwtLoaded.getTokenSigningKey());
} }
if (hasDefaultTokenSigningKey()) { if (hasDefaultTokenSigningKey() && !isFirstInstall()) {
log.warn("JWT token signing key is default. This is a security issue. Please, consider to set unique value"); log.warn("JWT token signing key is default. This is a security issue. Please, consider to set unique value");
} }
} }
@ -107,12 +124,20 @@ public class JwtSettingsServiceDefault implements JwtSettingsService {
} }
@Override @Override
public JwtSettings saveJwtSettings(JwtSettings jwtSettings){ public JwtSettings saveJwtSettings(JwtSettings jwtSettings) {
jwtSettingsValidator.validate(jwtSettings); jwtSettingsValidator.validate(jwtSettings);
AdminSettings adminJwtSettings = mapJwtToAdminSettings(jwtSettings); final AdminSettings adminJwtSettings = mapJwtToAdminSettings(jwtSettings);
final AdminSettings existedSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
if (existedSettings != null) {
adminJwtSettings.setId(existedSettings.getId());
}
log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored"); log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored");
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings);
tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, ComponentLifecycleEvent.UPDATED);
if (!isInstall()) {
tbClusterService.orElseThrow().broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, ComponentLifecycleEvent.UPDATED);
}
reloadJwtSettings(); reloadJwtSettings();
return getJwtSettings(); return getJwtSettings();
} }
@ -122,12 +147,7 @@ public class JwtSettingsServiceDefault implements JwtSettingsService {
} }
AdminSettings findJwtAdminSettings() { AdminSettings findJwtAdminSettings() {
try {
return adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); return adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
} catch (InvalidDataAccessResourceUsageException ignored) {
log.debug("findAdminSettingsByKey is returning InvalidDataAccessResourceUsageException. This is an installation case when the database is not initialized yet");
return null;
}
} }
/* /*

View File

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -68,6 +69,7 @@ public class AdminController extends BaseController {
@Autowired @Autowired
private SystemSecurityService systemSecurityService; private SystemSecurityService systemSecurityService;
@Lazy
@Autowired @Autowired
private JwtSettingsService jwtSettingsService; private JwtSettingsService jwtSettingsService;

View File

@ -35,6 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.config.jwt.JwtSettingsService;
import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -143,8 +144,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
EdgeNotificationService edgeNotificationService, EdgeNotificationService edgeNotificationService,
OtaPackageStateService firmwareStateService, OtaPackageStateService firmwareStateService,
GitVersionControlQueueService vcQueueService, GitVersionControlQueueService vcQueueService,
PartitionService partitionService) { PartitionService partitionService,
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); Optional<JwtSettingsService> jwtSettingsService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

View File

@ -70,6 +70,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -126,7 +127,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) { PartitionService partitionService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer()); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory; this.submitStrategyFactory = submitStrategyFactory;

View File

@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.config.jwt.JwtSettingsService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -76,11 +77,13 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final PartitionService partitionService; protected final PartitionService partitionService;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService;
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) { PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
this.actorContext = actorContext; this.actorContext = actorContext;
this.encodingService = encodingService; this.encodingService = encodingService;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
@ -89,6 +92,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.nfConsumer = nfConsumer; this.nfConsumer = nfConsumer;
this.jwtSettingsService = jwtSettingsService;
} }
public void init(String mainConsumerThreadName, String nfConsumerThreadName) { public void init(String mainConsumerThreadName, String nfConsumerThreadName) {
@ -172,6 +176,9 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
apiUsageStateService.onTenantProfileUpdate(tenantProfileId); apiUsageStateService.onTenantProfileUpdate(tenantProfileId);
} }
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (TenantId.SYS_TENANT_ID.equals(componentLifecycleMsg.getTenantId())) {
jwtSettingsService.ifPresent(JwtSettingsService::reloadJwtSettings);
} else {
tenantProfileCache.evict(componentLifecycleMsg.getTenantId()); tenantProfileCache.evict(componentLifecycleMsg.getTenantId());
partitionService.removeTenant(componentLifecycleMsg.getTenantId()); partitionService.removeTenant(componentLifecycleMsg.getTenantId());
if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) { if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
@ -179,6 +186,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
} else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) { } else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) {
apiUsageStateService.onTenantDelete((TenantId) componentLifecycleMsg.getEntityId()); apiUsageStateService.onTenantDelete((TenantId) componentLifecycleMsg.getEntityId());
} }
}
} else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId())); deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {