diff --git a/example/pom.xml b/example/pom.xml
index b50adcec..b034fa40 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -10,7 +10,7 @@
eu.webeid.example
web-eid-springboot-example
- 3.2.0
+ 3.2.1-SNAPSHOT
web-eid-springboot-example
Example Spring Boot application that demonstrates how to use Web eID for authentication and digital
signing
diff --git a/example/src/main/java/eu/webeid/example/config/SameSiteCookieConfiguration.java b/example/src/main/java/eu/webeid/example/config/CookieConfiguration.java
similarity index 64%
rename from example/src/main/java/eu/webeid/example/config/SameSiteCookieConfiguration.java
rename to example/src/main/java/eu/webeid/example/config/CookieConfiguration.java
index 74602523..8cb28694 100644
--- a/example/src/main/java/eu/webeid/example/config/SameSiteCookieConfiguration.java
+++ b/example/src/main/java/eu/webeid/example/config/CookieConfiguration.java
@@ -23,13 +23,16 @@
package eu.webeid.example.config;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
-public class SameSiteCookieConfiguration implements WebMvcConfigurer {
+public class CookieConfiguration implements WebMvcConfigurer {
@Bean
public TomcatContextCustomizer configureSameSiteCookies() {
@@ -39,4 +42,16 @@ public TomcatContextCustomizer configureSameSiteCookies() {
context.setCookieProcessor(cookieProcessor);
};
}
+
+ @Bean
+ @ConditionalOnExpression("'${web-eid-auth-token.validation.local-origin}'.startsWith('http:')")
+ public WebServerFactoryCustomizer httpSessionCookieCustomizer() {
+ return factory -> factory.addInitializers(servletContext -> servletContext.getSessionCookieConfig().setName("JSESSIONID"));
+ }
+
+ @Bean
+ @ConditionalOnExpression("'${web-eid-auth-token.validation.local-origin}'.startsWith('https:')")
+ public WebServerFactoryCustomizer httpsSessionCookieCustomizer() {
+ return factory -> factory.addInitializers(servletContext -> servletContext.getSessionCookieConfig().setName("__Host-JSESSIONID"));
+ }
}
diff --git a/example/src/main/java/eu/webeid/example/config/YAMLConfig.java b/example/src/main/java/eu/webeid/example/config/YAMLConfig.java
index 1c3359ae..cd4e7646 100644
--- a/example/src/main/java/eu/webeid/example/config/YAMLConfig.java
+++ b/example/src/main/java/eu/webeid/example/config/YAMLConfig.java
@@ -23,6 +23,13 @@
package eu.webeid.example.config;
import java.time.Duration;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -33,6 +40,8 @@
@ConfigurationProperties(prefix = "web-eid-auth-token.validation")
public class YAMLConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(YAMLConfig.class);
+
@Value("local-origin")
private String localOrigin;
@@ -52,6 +61,22 @@ public String getLocalOrigin() {
}
public void setLocalOrigin(String localOrigin) {
+ if (StringUtils.endsWith(localOrigin, "/")) {
+ throw new IllegalArgumentException("Configuration parameter local-origin cannot end with '/': " + localOrigin);
+ }
+ if (StringUtils.startsWith(localOrigin, "http:")) {
+ try {
+ if (InetAddress.getByName(new URI(localOrigin).getHost()).isLoopbackAddress()) {
+ this.localOrigin = localOrigin.replaceFirst("^http:", "https:");
+ LOG.warn("Configuration local-origin contains http protocol {}, which is not supported. Replacing it with secure {}", localOrigin, this.localOrigin);
+ return;
+ }
+ } catch (URISyntaxException e) {
+ LOG.error("Configuration parameter origin-local does not contain an URL: {}", localOrigin, e);
+ } catch (UnknownHostException e) {
+ LOG.error("Unable to determine if origin-local {} is loopback address", localOrigin, e);
+ }
+ }
this.localOrigin = localOrigin;
}
diff --git a/example/src/main/resources/application.properties b/example/src/main/resources/application.properties
index 7d70ac4e..cbb42d2a 100644
--- a/example/src/main/resources/application.properties
+++ b/example/src/main/resources/application.properties
@@ -1,2 +1 @@
spring.profiles.active=dev
-server.servlet.session.cookie.name=__Host-JSESSIONID
\ No newline at end of file
diff --git a/example/src/test/java/eu/webeid/example/config/CookieHttpTest.java b/example/src/test/java/eu/webeid/example/config/CookieHttpTest.java
new file mode 100644
index 00000000..fa406b6d
--- /dev/null
+++ b/example/src/test/java/eu/webeid/example/config/CookieHttpTest.java
@@ -0,0 +1,28 @@
+package eu.webeid.example.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.SessionCookieConfig;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.test.context.TestPropertySource;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@TestPropertySource(properties = {"web-eid-auth-token.validation.local-origin=http://localhost"})
+class CookieHttpTest {
+
+ @Autowired
+ private ServletWebServerApplicationContext context;
+
+ @Test
+ void whenLocalOriginStartsWithHttp_thenCookeDoesNotHaveHostPrefix() {
+ ServletContext servletContext = context.getServletContext();
+ SessionCookieConfig cookieConfig = servletContext.getSessionCookieConfig();
+ assertThat(cookieConfig.getName()).isEqualTo("JSESSIONID");
+ }
+
+}
diff --git a/example/src/test/java/eu/webeid/example/config/CookieHttpsTest.java b/example/src/test/java/eu/webeid/example/config/CookieHttpsTest.java
new file mode 100644
index 00000000..1040093a
--- /dev/null
+++ b/example/src/test/java/eu/webeid/example/config/CookieHttpsTest.java
@@ -0,0 +1,28 @@
+package eu.webeid.example.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.SessionCookieConfig;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.test.context.TestPropertySource;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@TestPropertySource(properties = {"web-eid-auth-token.validation.local-origin=https://localhost"})
+class CookieHttpsTest {
+
+ @Autowired
+ private ServletWebServerApplicationContext context;
+
+ @Test
+ void whenLocalOriginStartsWithHttp_thenCookeDoesNotHaveHostPrefix() {
+ ServletContext servletContext = context.getServletContext();
+ SessionCookieConfig cookieConfig = servletContext.getSessionCookieConfig();
+ assertThat(cookieConfig.getName()).isEqualTo("__Host-JSESSIONID");
+ }
+
+}
diff --git a/example/src/test/java/eu/webeid/example/config/YAMLConfigTest.java b/example/src/test/java/eu/webeid/example/config/YAMLConfigTest.java
new file mode 100644
index 00000000..b383de8b
--- /dev/null
+++ b/example/src/test/java/eu/webeid/example/config/YAMLConfigTest.java
@@ -0,0 +1,73 @@
+package eu.webeid.example.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class YAMLConfigTest {
+
+ @ValueSource(strings = {
+ "http://localhost",
+ "http://localhost:8080",
+ "http://127.0.0.1",
+ "http://127.0.0.1:8080",
+ "http://[::1]",
+ "http://[::1]:8080",
+ "http://[0000:0000:0000:0000:0000:0000:0000:0001]",
+ "http://[0000:0000:0000:0000:0000:0000:0000:0001]:8080"
+ })
+ @ParameterizedTest
+ void givenLocalOriginHttpLoopbackAddress_whenParsingLocalOrigin_thenItIsReplacedWithHttps(String origin) {
+ YAMLConfig yamlConfig = new YAMLConfig();
+ yamlConfig.setLocalOrigin(origin);
+ assertThat(yamlConfig.getLocalOrigin()).isEqualTo(origin.replaceFirst("^http:", "https:"));
+ }
+
+ @ValueSource(strings = {
+ "https://localhost",
+ "https://localhost:8080",
+ "https://127.0.0.1",
+ "https://127.0.0.1:8080",
+ "https://[::1]",
+ "https://[::1]:8080",
+ "https://[0000:0000:0000:0000:0000:0000:0000:0001]",
+ "https://[0000:0000:0000:0000:0000:0000:0000:0001]:8080"
+ })
+ @ParameterizedTest
+ void givenLocalOriginHttpsLoopbackAddress_whenParsingLocalOrigin_thenOriginalIsKept(String origin) {
+ YAMLConfig yamlConfig = new YAMLConfig();
+ yamlConfig.setLocalOrigin(origin);
+ assertThat(yamlConfig.getLocalOrigin()).isEqualTo(origin);
+ }
+
+ @ValueSource(strings = {
+ "http://somename.app",
+ "http://somename.app:8080",
+ "http://8.8.8.8",
+ "http://8.8.8.8:8080",
+ "http://[2001:4860:4860::8888]",
+ "http://[2001:4860:4860::8888]:8080",
+ })
+ @ParameterizedTest
+ void givenLocalOriginHttpNonLoopbackAddress_whenParsingLocalOrigin_thenOriginalIsKept(String origin) {
+ YAMLConfig yamlConfig = new YAMLConfig();
+ yamlConfig.setLocalOrigin(origin);
+ assertThat(yamlConfig.getLocalOrigin()).isEqualTo(origin);
+ }
+
+ @ValueSource(strings = {
+ "https://localhost/",
+ "https://localhost:8080/"
+ })
+ @ParameterizedTest
+ void givenLocalOriginThatEndsWithSlash_whenParsingLocalOrigin_thenExceptionIsThrown(String origin) {
+ YAMLConfig yamlConfig = new YAMLConfig();
+ assertThatThrownBy(() -> yamlConfig.setLocalOrigin(origin))
+ .hasMessage("Configuration parameter local-origin cannot end with '/': " + origin);
+ }
+}