Skip to content

Commit

Permalink
feat: The authentication tokens (JWT) now retain their validity beyon…
Browse files Browse the repository at this point in the history
…d the restart of the IRIS client. This means that, ideally, users notice only little of a restart of the application.

If no secrets for JWT have been set via external properties, then secure ones are automatically generated and stored in the database. This means that the secrets and the JWTs are retained beyond the restart. After a configurable duration (default: 3 months) they are renewed.

PR #804
  • Loading branch information
jekutzsche authored Jun 21, 2022
1 parent b20ed86 commit 2442685
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package iris.client_bff.core.settings;

import org.springframework.data.jpa.repository.JpaRepository;

public interface SettingsRepository extends JpaRepository<Setting, Setting.Name> {}
7 changes: 5 additions & 2 deletions iris-client-bff/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE settings (
name varchar(50) primary key,
stored_value varchar(1000) NOT NULL,
saved_at date NOT NULL
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE settings (
name varchar(50) primary key,
stored_value varchar(1000) NOT NULL,
saved_at date NOT NULL
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE settings (
name varchar(50) primary key,
stored_value varchar(1000) NOT NULL,
saved_at date NOT NULL
);

0 comments on commit 2442685

Please sign in to comment.