diff --git a/iris-client-bff/src/main/java/iris/client_bff/auth/db/jwt/JWTService.java b/iris-client-bff/src/main/java/iris/client_bff/auth/db/jwt/JWTService.java index b75294ca6..62bc98ccb 100644 --- a/iris-client-bff/src/main/java/iris/client_bff/auth/db/jwt/JWTService.java +++ b/iris-client-bff/src/main/java/iris/client_bff/auth/db/jwt/JWTService.java @@ -6,16 +6,26 @@ import io.vavr.control.Option; import io.vavr.control.Try; +import iris.client_bff.core.settings.Setting; +import iris.client_bff.core.settings.Setting.Name; +import iris.client_bff.core.settings.SettingsRepository; import lombok.Value; +import lombok.experimental.NonFinal; import lombok.extern.slf4j.Slf4j; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.time.Duration; import java.time.Instant; +import java.time.LocalDate; +import java.time.Period; import java.util.Date; import java.util.List; +import java.util.Random; import java.util.function.Function; +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -24,7 +34,9 @@ import javax.validation.constraints.NotNull; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.hibernate.validator.constraints.time.DurationMin; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConstructorBinding; @@ -247,9 +259,18 @@ private String hashToken(String jwt) { @Value static class Properties { - @NotBlank + private static final Random random = new SecureRandom(); + + @Autowired + SettingsRepository settings; + + @Nullable + @NonFinal String sharedSecret; + @NotNull + Period saveSecretFor; + @NotNull @DurationMin(seconds = 1) Duration expirationTime; @@ -268,14 +289,48 @@ String getSameSiteStr() { @NotNull @Valid RefreshProperties refresh; + + @PostConstruct + void initSecrets() { + + if (isBlank(sharedSecret)) { + + var setting = settings.findById(Name.JWT_SECRET) + .filter(it -> it.getSavedAt().plus(saveSecretFor).isAfter(LocalDate.now())) + .orElseGet(() -> createNewRandomSecret(Name.JWT_SECRET)); + + sharedSecret = setting.getStoredValue(); + } + + if (isBlank(refresh.getSharedSecret())) { + + var setting = settings.findById(Name.REFRESH_SECRET) + .filter(it -> it.getSavedAt().plus(refresh.getSaveSecretFor()).isAfter(LocalDate.now())) + .orElseGet(() -> createNewRandomSecret(Name.REFRESH_SECRET)); + + refresh.sharedSecret = setting.getStoredValue(); + } + } + + private Setting createNewRandomSecret(Name name) { + + // 64 Bytes of printable Ascii characters + var secret = RandomStringUtils.random(64, 32, 127, false, false, null, random); + + return settings.save(new Setting(name, secret, LocalDate.now())); + } } @Value static class RefreshProperties { - @NotBlank + @Nullable + @NonFinal String sharedSecret; + @NotNull + Period saveSecretFor; + @NotNull @DurationMin(seconds = 1) Duration expirationTime; diff --git a/iris-client-bff/src/main/java/iris/client_bff/core/settings/Setting.java b/iris-client-bff/src/main/java/iris/client_bff/core/settings/Setting.java new file mode 100644 index 000000000..fdef7141f --- /dev/null +++ b/iris-client-bff/src/main/java/iris/client_bff/core/settings/Setting.java @@ -0,0 +1,40 @@ +package iris.client_bff.core.settings; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.Size; + +/** + * @author Jens Kutzsche + */ +@Entity +@Table(name = "settings") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Setting { + + @Id + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Name name; + + @Size(max = 1000) + private String storedValue; // value is a keyword for DBMS + + private LocalDate savedAt = LocalDate.now(); + + public enum Name { + JWT_SECRET, REFRESH_SECRET; + } +} diff --git a/iris-client-bff/src/main/java/iris/client_bff/core/settings/SettingsRepository.java b/iris-client-bff/src/main/java/iris/client_bff/core/settings/SettingsRepository.java new file mode 100644 index 000000000..d70330c5a --- /dev/null +++ b/iris-client-bff/src/main/java/iris/client_bff/core/settings/SettingsRepository.java @@ -0,0 +1,5 @@ +package iris.client_bff.core.settings; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SettingsRepository extends JpaRepository {} diff --git a/iris-client-bff/src/main/resources/application.properties b/iris-client-bff/src/main/resources/application.properties index cbf4b6dee..db55502ea 100644 --- a/iris-client-bff/src/main/resources/application.properties +++ b/iris-client-bff/src/main/resources/application.properties @@ -69,12 +69,15 @@ spring.jackson.default-property-inclusion=NON_ABSENT springdoc.api-docs.enabled=false -security.jwt.shared-secret=${random.value}${random.value} +# A random value generated at runtime if nothing is set here. +security.jwt.shared-secret= +security.jwt.save-secret-for=3m security.jwt.expiration-time=1h security.jwt.cookie-name=IRIS_JWT security.jwt.set-secure=true security.jwt.same-site=Strict -security.jwt.refresh.shared-secret=${random.value}${random.value} +security.jwt.refresh.shared-secret= +security.jwt.refresh.save-secret-for=3m security.jwt.refresh.expiration-time=24h security.jwt.refresh.cookie-name=IRIS_REFRESH_JWT # Durations >0 like descripted in https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.typesafe-configuration-properties.conversion diff --git a/iris-client-bff/src/main/resources/db/migration/V1014__add_settings.sql b/iris-client-bff/src/main/resources/db/migration/V1014__add_settings.sql new file mode 100644 index 000000000..f0be4ef71 --- /dev/null +++ b/iris-client-bff/src/main/resources/db/migration/V1014__add_settings.sql @@ -0,0 +1,5 @@ +CREATE TABLE settings ( + name varchar(50) primary key, + stored_value varchar(1000) NOT NULL, + saved_at date NOT NULL +); diff --git a/iris-client-bff/src/main/resources/db/migration_mssql/V1014__add_settings.sql b/iris-client-bff/src/main/resources/db/migration_mssql/V1014__add_settings.sql new file mode 100644 index 000000000..f0be4ef71 --- /dev/null +++ b/iris-client-bff/src/main/resources/db/migration_mssql/V1014__add_settings.sql @@ -0,0 +1,5 @@ +CREATE TABLE settings ( + name varchar(50) primary key, + stored_value varchar(1000) NOT NULL, + saved_at date NOT NULL +); diff --git a/iris-client-bff/src/main/resources/db/migration_mysql/V1014__add_settings.sql b/iris-client-bff/src/main/resources/db/migration_mysql/V1014__add_settings.sql new file mode 100644 index 000000000..f0be4ef71 --- /dev/null +++ b/iris-client-bff/src/main/resources/db/migration_mysql/V1014__add_settings.sql @@ -0,0 +1,5 @@ +CREATE TABLE settings ( + name varchar(50) primary key, + stored_value varchar(1000) NOT NULL, + saved_at date NOT NULL +);