Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cbd3a9e25 | ||
|
|
4c914d46c9 | ||
|
|
adf411ecc3 | ||
|
|
95b39a203f | ||
|
|
3d653b3b50 | ||
|
|
938fd3c2e5 | ||
|
|
45bb0f9b0c | ||
|
|
cddd84d564 | ||
|
|
6931d40e6e | ||
|
|
3b672787f3 | ||
|
|
c0ee52b33b | ||
|
|
68f8641233 | ||
|
|
e7b2af47e1 | ||
|
|
1ad6cbd7f8 | ||
|
|
195af52d0b | ||
|
|
bc9d5f1299 | ||
|
|
3a4345eb6a | ||
|
|
6c41dea893 | ||
|
|
ee1d5b3b3c | ||
|
|
89a4255679 | ||
|
|
6d2e51a0b9 | ||
|
|
798d398d9b | ||
|
|
085554f56b | ||
|
|
45b3b35db7 | ||
|
|
2d06e1159c | ||
|
|
927008bdc8 | ||
|
|
30588dc3c8 | ||
|
|
2f79da00dc | ||
|
|
e2abe36fa8 | ||
|
|
456fd3adb4 |
16
.travis.yml
16
.travis.yml
@@ -1,20 +1,16 @@
|
||||
language: java
|
||||
|
||||
sudo: required
|
||||
|
||||
services: docker
|
||||
|
||||
jdk: oraclejdk8
|
||||
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk8
|
||||
- openjdk11
|
||||
services:
|
||||
- docker
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
install: true
|
||||
|
||||
script: ./gradlew clean check --no-daemon --stacktrace
|
||||
|
||||
3
Jenkinsfile
vendored
3
Jenkinsfile
vendored
@@ -120,7 +120,8 @@ try {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
||||
sh './gradlew deployArtifacts finalizeDeployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
||||
sh './gradlew deployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
||||
sh './gradlew finalizeDeployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||
|
||||
springBootVersion = '2.2.0.M6'
|
||||
springBootVersion = '2.2.4.RELEASE'
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=true
|
||||
version=2.2.0.RC1
|
||||
version=2.3.0.M1
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-RELEASE'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-SR4'
|
||||
mavenBom 'org.junit:junit-bom:5.5.2'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.2.0.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-RELEASE'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.RELEASE'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.12.0'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.2.3.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Neumann-M2'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.3.0.M1'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.12.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.2') {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.5') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -32,7 +32,7 @@ public class PrincipalNameIndexResolver<S extends Session> extends SingleIndexRe
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
private static final Expression expression = new SpelExpressionParser().parseExpression("authentication?.name");
|
||||
|
||||
public PrincipalNameIndexResolver() {
|
||||
super(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
@@ -45,7 +45,6 @@ public class PrincipalNameIndexResolver<S extends Session> extends SingleIndexRe
|
||||
}
|
||||
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
||||
if (authentication != null) {
|
||||
Expression expression = parser.parseExpression("authentication?.name");
|
||||
return expression.getValue(authentication, String.class);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -62,6 +63,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
domainValid.set('-');
|
||||
}
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
private String cookieName = "SESSION";
|
||||
|
||||
private Boolean useSecureCookie;
|
||||
@@ -121,7 +124,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
public void writeCookieValue(CookieValue cookieValue) {
|
||||
HttpServletRequest request = cookieValue.getRequest();
|
||||
HttpServletResponse response = cookieValue.getResponse();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.cookieName).append('=');
|
||||
String value = getValue(cookieValue);
|
||||
@@ -132,8 +134,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
int maxAge = getMaxAge(cookieValue);
|
||||
if (maxAge > -1) {
|
||||
sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
|
||||
OffsetDateTime expires = (maxAge != 0) ? OffsetDateTime.now().plusSeconds(maxAge)
|
||||
: Instant.EPOCH.atOffset(ZoneOffset.UTC);
|
||||
ZonedDateTime expires = (maxAge != 0) ? ZonedDateTime.now(this.clock).plusSeconds(maxAge)
|
||||
: Instant.EPOCH.atZone(ZoneOffset.UTC);
|
||||
sb.append("; Expires=").append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
|
||||
}
|
||||
String domain = getDomainName(request);
|
||||
@@ -155,7 +157,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
if (this.sameSite != null) {
|
||||
sb.append("; SameSite=").append(this.sameSite);
|
||||
}
|
||||
|
||||
response.addHeader("Set-Cookie", sb.toString());
|
||||
}
|
||||
|
||||
@@ -259,6 +260,10 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
void setClock(Clock clock) {
|
||||
this.clock = clock.withZone(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a Cookie marked as secure should be used. The default is to use the value
|
||||
* of {@link HttpServletRequest#isSecure()}.
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
@@ -71,11 +76,10 @@ class DefaultCookieSerializerTests {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesSingle(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@@ -84,81 +88,73 @@ class DefaultCookieSerializerTests {
|
||||
this.sessionId = "&^%$*";
|
||||
this.serializer.setUseBase64Encoding(true);
|
||||
this.request.setCookies(new Cookie(this.cookieName, this.sessionId));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesSingleAndInvalidName(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName + "INVALID", this.sessionId + "INVALID", useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesMulti(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String secondSession = "secondSessionId";
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName, secondSession, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsExactly(this.sessionId, secondSession);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesMultiCustomSessionCookieName(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
setCookieName("JSESSIONID");
|
||||
String secondSession = "secondSessionId";
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName, secondSession, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsExactly(this.sessionId, secondSession);
|
||||
}
|
||||
|
||||
// gh-392
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesNullCookieValue(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesNullCookieValueAndJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setJvmRoute("123");
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieValuesNullCookieValueAndNotNullCookie(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.serializer.setJvmRoute("123");
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding),
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
// --- writeCookie ---
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void writeCookie(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookieValue(useBase64Encoding)).isEqualTo(this.sessionId);
|
||||
}
|
||||
|
||||
@@ -167,25 +163,20 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieHttpOnlyDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieHttpOnlySetTrue() {
|
||||
this.serializer.setUseHttpOnlyCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieHttpOnlySetFalse() {
|
||||
this.serializer.setUseHttpOnlyCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isFalse();
|
||||
}
|
||||
|
||||
@@ -194,7 +185,6 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieDomainNameDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
}
|
||||
|
||||
@@ -202,9 +192,7 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieDomainNameCustom() {
|
||||
String domainName = "example.com";
|
||||
this.serializer.setDomainName(domainName);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isEqualTo(domainName);
|
||||
}
|
||||
|
||||
@@ -221,22 +209,18 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieDomainNamePattern() {
|
||||
String domainNamePattern = "^.+?\\.(\\w+\\.[a-z]+)$";
|
||||
this.serializer.setDomainNamePattern(domainNamePattern);
|
||||
|
||||
String[] matchingDomains = { "child.sub.example.com", "www.example.com" };
|
||||
for (String domain : matchingDomains) {
|
||||
this.request.setServerName(domain);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
assertThat(getCookie().getDomain()).isEqualTo("example.com");
|
||||
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
String[] notMatchingDomains = { "example.com", "localhost", "127.0.0.1" };
|
||||
for (String domain : notMatchingDomains) {
|
||||
this.request.setServerName(domain);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
}
|
||||
@@ -253,7 +237,6 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieCookieNameDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo("SESSION");
|
||||
}
|
||||
|
||||
@@ -261,9 +244,7 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieCookieNameCustom() {
|
||||
String cookieName = "JSESSIONID";
|
||||
setCookieName(cookieName);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo(cookieName);
|
||||
}
|
||||
|
||||
@@ -278,18 +259,14 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieCookiePathDefaultEmptyContextPathUsed() {
|
||||
this.request.setContextPath("");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieCookiePathDefaultContextPathUsed() {
|
||||
this.request.setContextPath("/context");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@@ -297,9 +274,7 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieCookiePathExplicitNullCookiePathContextPathUsed() {
|
||||
this.request.setContextPath("/context");
|
||||
this.serializer.setCookiePath(null);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@@ -307,9 +282,7 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieCookiePathExplicitCookiePath() {
|
||||
this.request.setContextPath("/context");
|
||||
this.serializer.setCookiePath("/");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
@@ -318,36 +291,45 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieCookieMaxAgeDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(-1);
|
||||
assertThat(getCookie().getExpires()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieCookieMaxAgeExplicit() {
|
||||
this.serializer.setClock(Clock.fixed(Instant.parse("2019-10-07T20:10:00Z"), ZoneOffset.UTC));
|
||||
this.serializer.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
MockCookie cookie = getCookie();
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(100);
|
||||
ZonedDateTime expires = cookie.getExpires();
|
||||
assertThat(expires).isNotNull();
|
||||
assertThat(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME)).isEqualTo("Mon, 7 Oct 2019 20:11:40 GMT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieCookieMaxAgeExplicitEmptyCookie() {
|
||||
this.serializer.setClock(Clock.fixed(Instant.parse("2019-10-07T20:10:00Z"), ZoneOffset.UTC));
|
||||
this.serializer.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(""));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(0);
|
||||
MockCookie cookie = getCookie();
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(0);
|
||||
ZonedDateTime expires = cookie.getExpires();
|
||||
assertThat(expires).isNotNull();
|
||||
assertThat(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME)).isEqualTo("Thu, 1 Jan 1970 00:00:00 GMT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieCookieMaxAgeExplicitCookieValue() {
|
||||
this.serializer.setClock(Clock.fixed(Instant.parse("2019-10-07T20:10:00Z"), ZoneOffset.UTC));
|
||||
CookieValue cookieValue = cookieValue(this.sessionId);
|
||||
cookieValue.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue);
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
MockCookie cookie = getCookie();
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(100);
|
||||
ZonedDateTime expires = cookie.getExpires();
|
||||
assertThat(expires).isNotNull();
|
||||
assertThat(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME)).isEqualTo("Mon, 7 Oct 2019 20:11:40 GMT");
|
||||
}
|
||||
|
||||
// --- secure ---
|
||||
@@ -355,7 +337,6 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieDefaultInsecureRequest() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@@ -363,18 +344,14 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieSecureSecureRequest() {
|
||||
this.request.setSecure(true);
|
||||
this.serializer.setUseSecureCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieSecureInsecureRequest() {
|
||||
this.serializer.setUseSecureCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@@ -382,65 +359,56 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieInsecureSecureRequest() {
|
||||
this.request.setSecure(true);
|
||||
this.serializer.setUseSecureCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCookieInecureInsecureRequest() {
|
||||
this.serializer.setUseSecureCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
// --- jvmRoute ---
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void writeCookieJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookieValue(useBase64Encoding)).isEqualTo(this.sessionId + "." + jvmRoute);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId + "." + jvmRoute, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieJvmRouteRouteMissing(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
@ValueSource(booleans = { true, false })
|
||||
void readCookieJvmRouteOnlyRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(createCookie(this.cookieName, "." + jvmRoute, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly("");
|
||||
}
|
||||
|
||||
@@ -451,7 +419,6 @@ class DefaultCookieSerializerTests {
|
||||
this.request.setAttribute("rememberMe", true);
|
||||
this.serializer.setRememberMeRequestAttribute("rememberMe");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@@ -462,7 +429,6 @@ class DefaultCookieSerializerTests {
|
||||
CookieValue cookieValue = cookieValue(this.sessionId);
|
||||
cookieValue.setCookieMaxAge(100);
|
||||
this.serializer.writeCookieValue(cookieValue);
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
}
|
||||
|
||||
@@ -471,7 +437,6 @@ class DefaultCookieSerializerTests {
|
||||
@Test
|
||||
void writeCookieDefaultSameSiteLax() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
|
||||
}
|
||||
|
||||
@@ -479,7 +444,6 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieSetSameSiteLax() {
|
||||
this.serializer.setSameSite("Lax");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
|
||||
}
|
||||
|
||||
@@ -487,7 +451,6 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieSetSameSiteStrict() {
|
||||
this.serializer.setSameSite("Strict");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Strict");
|
||||
}
|
||||
|
||||
@@ -495,7 +458,6 @@ class DefaultCookieSerializerTests {
|
||||
void writeCookieSetSameSiteNull() {
|
||||
this.serializer.setSameSite(null);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isNull();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= Spring Session - Custom Cookie
|
||||
Rob Winch
|
||||
Rob Winch; Eleftheria Stein-Kousathana
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Spring Session to use custom cookies with Java Configuration.
|
||||
@@ -58,6 +58,9 @@ See `domainNamePattern` as an alternative.
|
||||
The pattern should provide a single grouping that is used to extract the value of the cookie domain.
|
||||
If the regular expression does not match, no domain is set and the existing domain is used.
|
||||
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] is used as the domain.
|
||||
* `sameSite`: The value for the `SameSite` cookie directive.
|
||||
To disable the serialization of the `SameSite` cookie directive, you may set this value to `null`.
|
||||
Default: `Lax`
|
||||
|
||||
WARNING: You should only match on valid domain characters, since the domain name is reflected in the response.
|
||||
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= Spring Session
|
||||
Rob Winch; Vedran Pavić; Jay Bryant
|
||||
Rob Winch; Vedran Pavić; Jay Bryant; Eleftheria Stein-Kousathana
|
||||
:doctype: book
|
||||
:indexdoc-tests: {docs-test-dir}docs/IndexDocTests.java
|
||||
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
|
||||
@@ -596,6 +596,7 @@ You can browse the complete link:../../api/[Javadoc] online. The key APIs are de
|
||||
* <<api-reactivemapsessionrepository>>
|
||||
* <<api-jdbcindexedsessionrepository>>
|
||||
* <<api-hazelcastindexedsessionrepository>>
|
||||
* <<api-cookieserializer>>
|
||||
|
||||
[[api-session]]
|
||||
=== Using `Session`
|
||||
@@ -1230,6 +1231,68 @@ Note that if you use Hazelcast's `MapStore` to persist your sessions `IMap`, the
|
||||
* Reloading triggers `EntryAddedListener` results in `SessionCreatedEvent` being re-published
|
||||
* Reloading uses default TTL for a given `IMap` results in sessions losing their original TTL
|
||||
|
||||
[[api-cookieserializer]]
|
||||
=== Using `CookieSerializer`
|
||||
|
||||
A `CookieSerializer` is responsible for defining how the session cookie is written.
|
||||
Spring Session comes with a default implementation using `DefaultCookieSerializer`.
|
||||
|
||||
[[api-cookieserializer-bean]]
|
||||
==== Exposing `CookieSerializer` as a bean
|
||||
Exposing the `CookieSerializer` as a Spring bean augments the existing configuration when you use configurations like `@EnableRedisHttpSession`.
|
||||
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}spring-session-sample-javaconfig-custom-cookie/src/main/java/sample/Config.java[tags=cookie-serializer]
|
||||
----
|
||||
|
||||
<1> We customize the name of the cookie to be `JSESSIONID`.
|
||||
<2> We customize the path of the cookie to be `/` (rather than the default of the context root).
|
||||
<3> We customize the domain name pattern (a regular expression) to be `^.+?\\.(\\w+\\.[a-z]+)$`.
|
||||
This allows sharing a session across domains and applications.
|
||||
If the regular expression does not match, no domain is set and the existing domain is used.
|
||||
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] is used as the domain.
|
||||
This means that a request to https://child.example.com sets the domain to `example.com`.
|
||||
However, a request to http://localhost:8080/ or https://192.168.1.100:8080/ leaves the cookie unset and, thus, still works in development without any changes being necessary for production.
|
||||
====
|
||||
|
||||
WARNING: You should only match on valid domain characters, since the domain name is reflected in the response.
|
||||
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||
|
||||
[[api-cookieserializer-customization]]
|
||||
==== Customizing `CookieSerializer`
|
||||
|
||||
You can customize how the session cookie is written by using any of the following configuration options on the `DefaultCookieSerializer`.
|
||||
|
||||
* `cookieName`: The name of the cookie to use.
|
||||
Default: `SESSION`.
|
||||
* `useSecureCookie`: Specifies whether a secure cookie should be used.
|
||||
Default: Use the value of `HttpServletRequest.isSecure()` at the time of creation.
|
||||
* `cookiePath`: The path of the cookie.
|
||||
Default: The context root.
|
||||
* `cookieMaxAge`: Specifies the max age of the cookie to be set at the time the session is created.
|
||||
Default: `-1`, which indicates the cookie should be removed when the browser is closed.
|
||||
* `jvmRoute`: Specifies a suffix to be appended to the session ID and included in the cookie.
|
||||
Used to identify which JVM to route to for session affinity.
|
||||
With some implementations (that is, Redis) this option provides no performance benefit.
|
||||
However, it can help with tracing logs of a particular user.
|
||||
* `domainName`: Allows specifying a specific domain name to be used for the cookie.
|
||||
This option is simple to understand but often requires a different configuration between development and production environments.
|
||||
See `domainNamePattern` as an alternative.
|
||||
* `domainNamePattern`: A case-insensitive pattern used to extract the domain name from the `HttpServletRequest#getServerName()`.
|
||||
The pattern should provide a single grouping that is used to extract the value of the cookie domain.
|
||||
If the regular expression does not match, no domain is set and the existing domain is used.
|
||||
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] is used as the domain.
|
||||
* `sameSite`: The value for the `SameSite` cookie directive.
|
||||
To disable the serialization of the `SameSite` cookie directive, you may set this value to `null`.
|
||||
Default: `Lax`
|
||||
|
||||
WARNING: You should only match on valid domain characters, since the domain name is reflected in the response.
|
||||
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||
|
||||
[[custom-sessionrepository]]
|
||||
== Customing `SessionRepository`
|
||||
|
||||
|
||||
@@ -35,16 +35,13 @@ public class HazelcastHttpSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
Config config = new Config();
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
Config config = new Config();
|
||||
|
||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
|
||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config); // <3>
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.testcontainers.containers.BindMode;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -46,10 +46,9 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@WebAppConfiguration
|
||||
class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests {
|
||||
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.2")
|
||||
.withExposedPorts(5701).withEnv("JAVA_OPTS", "-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")
|
||||
.withClasspathResourceMapping("/hazelcast-server.xml", "/opt/hazelcast/config_ext/hazelcast.xml",
|
||||
BindMode.READ_ONLY);
|
||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.3")
|
||||
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||
"/opt/hazelcast/hazelcast.xml");
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() {
|
||||
|
||||
@@ -23,49 +23,32 @@ import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* Utility class for Hazelcast integration tests.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public final class HazelcastITestUtils {
|
||||
final class HazelcastITestUtils {
|
||||
|
||||
private HazelcastITestUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link HazelcastInstance} for use in integration tests.
|
||||
* @param port the port for Hazelcast to bind to
|
||||
* @return the Hazelcast instance
|
||||
*/
|
||||
public static HazelcastInstance embeddedHazelcastServer(int port) {
|
||||
static HazelcastInstance embeddedHazelcastServer() {
|
||||
Config config = new Config();
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
Config config = new Config();
|
||||
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
|
||||
networkConfig.setPort(port);
|
||||
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
|
||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link HazelcastInstance} for use in integration tests.
|
||||
* @return the Hazelcast instance
|
||||
*/
|
||||
public static HazelcastInstance embeddedHazelcastServer() {
|
||||
return embeddedHazelcastServer(SocketUtils.findAvailableTcpPort());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.hazelcast.config.annotation.web.http;
|
||||
package org.springframework.session.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -38,8 +38,7 @@ import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.session.hazelcast.HazelcastITestUtils;
|
||||
import org.springframework.session.hazelcast.SessionEventRegistry;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -57,7 +56,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class EnableHazelcastHttpSessionEventsTests<S extends Session> {
|
||||
class SessionEventHazelcastIndexedSessionRepositoryTests<S extends Session> {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
public class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private Map<String, AbstractSessionEvent> events = new HashMap<>();
|
||||
|
||||
@@ -40,17 +40,17 @@ public class SessionEventRegistry implements ApplicationListener<AbstractSession
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
void clear() {
|
||||
this.events.clear();
|
||||
this.locks.clear();
|
||||
}
|
||||
|
||||
public boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||
boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||
return waitForEvent(sessionId) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractSessionEvent> E getEvent(String sessionId) throws InterruptedException {
|
||||
<E extends AbstractSessionEvent> E getEvent(String sessionId) throws InterruptedException {
|
||||
return (E) waitForEvent(sessionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.hazelcast.config.annotation.web.http;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.hazelcast.config.ClasspathXmlConfig;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test the different configuration options for the {@link EnableHazelcastHttpSession}
|
||||
* annotation.
|
||||
*
|
||||
* @author Tommy Ludwig
|
||||
*/
|
||||
public class HazelcastHttpSessionConfigurationXmlTests<S extends Session> {
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
static class CustomXmlMapNameTest<S extends Session> {
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Test
|
||||
void saveSessionTest() {
|
||||
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
S session = this.repository.findById(sessionToSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofMinutes(30));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(sessionMapName = "my-sessions")
|
||||
static class HazelcastSessionXmlConfigCustomMapName {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance embeddedHazelcast() {
|
||||
Config hazelcastConfig = new ClasspathXmlConfig(
|
||||
"org/springframework/session/hazelcast/config/annotation/web/http/hazelcast-custom-map-name.xml");
|
||||
NetworkConfig netConfig = new NetworkConfig();
|
||||
netConfig.setPort(SocketUtils.findAvailableTcpPort());
|
||||
hazelcastConfig.setNetworkConfig(netConfig);
|
||||
return Hazelcast.newHazelcastInstance(hazelcastConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
static class CustomXmlMapNameAndIdleTest<S extends Session> {
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Test
|
||||
void saveSessionTest() {
|
||||
|
||||
S sessionToSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(sessionToSave);
|
||||
|
||||
S session = this.repository.findById(sessionToSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofMinutes(20));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(sessionMapName = "test-sessions", maxInactiveIntervalInSeconds = 1200)
|
||||
static class HazelcastSessionXmlConfigCustomMapNameAndIdle {
|
||||
|
||||
@Bean
|
||||
HazelcastInstance embeddedHazelcast() {
|
||||
Config hazelcastConfig = new ClasspathXmlConfig(
|
||||
"org/springframework/session/hazelcast/config/annotation/web/http/hazelcast-custom-idle-time-map-name.xml");
|
||||
NetworkConfig netConfig = new NetworkConfig();
|
||||
netConfig.setPort(SocketUtils.findAvailableTcpPort());
|
||||
hazelcastConfig.setNetworkConfig(netConfig);
|
||||
return Hazelcast.newHazelcastInstance(hazelcastConfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,12 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
|
||||
|
||||
<network>
|
||||
<join>
|
||||
<multicast enabled="false"/>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<user-code-deployment enabled="true">
|
||||
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-idle-time-map-name</name>
|
||||
<password>test-pass</password>
|
||||
</group>
|
||||
|
||||
<network>
|
||||
<port auto-increment="true" port-count="100">5701</port>
|
||||
<outbound-ports>
|
||||
<ports>0</ports>
|
||||
</outbound-ports>
|
||||
<join>
|
||||
<multicast enabled="false"/>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<map name="test-sessions">
|
||||
<in-memory-format>BINARY</in-memory-format>
|
||||
<backup-count>1</backup-count>
|
||||
<async-backup-count>0</async-backup-count>
|
||||
<time-to-live-seconds>0</time-to-live-seconds>
|
||||
<max-idle-seconds>300</max-idle-seconds>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
|
||||
<attributes>
|
||||
<attribute extractor="org.springframework.session.hazelcast.PrincipalNameExtractor">principalName</attribute>
|
||||
</attributes>
|
||||
<indexes>
|
||||
<index ordered="false">principalName</index>
|
||||
</indexes>
|
||||
</map>
|
||||
|
||||
</hazelcast>
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-map-name</name>
|
||||
<password>test-pass</password>
|
||||
</group>
|
||||
|
||||
<network>
|
||||
<port auto-increment="true" port-count="100">5701</port>
|
||||
<outbound-ports>
|
||||
<ports>0</ports>
|
||||
</outbound-ports>
|
||||
<join>
|
||||
<multicast enabled="false"/>
|
||||
</join>
|
||||
</network>
|
||||
|
||||
<map name="my-sessions">
|
||||
<in-memory-format>BINARY</in-memory-format>
|
||||
<backup-count>1</backup-count>
|
||||
<async-backup-count>0</async-backup-count>
|
||||
<time-to-live-seconds>0</time-to-live-seconds>
|
||||
<max-idle-seconds>0</max-idle-seconds>
|
||||
<merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
|
||||
<attributes>
|
||||
<attribute extractor="org.springframework.session.hazelcast.PrincipalNameExtractor">principalName</attribute>
|
||||
</attributes>
|
||||
<indexes>
|
||||
<index ordered="false">principalName</index>
|
||||
</indexes>
|
||||
</map>
|
||||
|
||||
</hazelcast>
|
||||
@@ -1,10 +1,3 @@
|
||||
ext['jackson.version'] = '2.10.0'
|
||||
ext['reactor-bom.version'] = 'Dysprosium-RELEASE'
|
||||
ext['spring-data-releasetrain.version'] = 'Moore-RELEASE'
|
||||
ext['spring-framework.version'] = '5.2.0.RELEASE'
|
||||
ext['spring-security.version'] = '5.2.0.RELEASE'
|
||||
ext['webjars-locator-core.version'] = '0.38'
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.10.0'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Description</h1>
|
||||
<p>This application demonstrates how to use a Redis instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
||||
<p>This application demonstrates how to customize the session cookie. Notice that the name of the cookie is JSESSIONID.</p>
|
||||
|
||||
<h1>Try it</h1>
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.hazelcast.nio.ObjectDataInput;
|
||||
import com.hazelcast.nio.ObjectDataOutput;
|
||||
import com.hazelcast.nio.serialization.StreamSerializer;
|
||||
|
||||
/**
|
||||
* A {@link StreamSerializer} that uses Java serialization to persist the session. This is
|
||||
* certainly not the most efficient way to persist sessions, but the example is intended
|
||||
* to demonstrate using minimal dependencies. For better serialization methods try using
|
||||
* <a href="https://github.com/EsotericSoftware/kryo">Kryo</a>.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class ObjectStreamSerializer implements StreamSerializer<Object> {
|
||||
|
||||
@Override
|
||||
public int getTypeId() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ObjectDataOutput objectDataOutput, Object object) throws IOException {
|
||||
ObjectOutputStream out = new ObjectOutputStream((OutputStream) objectDataOutput);
|
||||
out.writeObject(object);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(ObjectDataInput objectDataInput) throws IOException {
|
||||
ObjectInputStream in = new ObjectInputStream((InputStream) objectDataInput);
|
||||
try {
|
||||
return in.readObject();
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package sample;
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.MapAttributeConfig;
|
||||
import com.hazelcast.config.MapIndexConfig;
|
||||
import com.hazelcast.config.SerializerConfig;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
// tag::class[]
|
||||
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = 300)
|
||||
@@ -38,26 +37,15 @@ public class SessionConfig {
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
Config config = new Config();
|
||||
|
||||
int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
config.getNetworkConfig().setPort(port).getJoin().getMulticastConfig().setEnabled(false);
|
||||
|
||||
System.out.println("Hazelcast port #: " + port);
|
||||
|
||||
SerializerConfig serializer = new SerializerConfig().setImplementation(new ObjectStreamSerializer())
|
||||
.setTypeClass(Object.class);
|
||||
|
||||
config.getSerializationConfig().addSerializerConfig(serializer);
|
||||
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
MapAttributeConfig attributeConfig = new MapAttributeConfig()
|
||||
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||
.setExtractor(PrincipalNameExtractor.class.getName());
|
||||
|
||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package sample;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -28,6 +26,7 @@ import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.annotation.WebListener;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.config.NetworkConfig;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
@@ -62,21 +61,11 @@ public class Initializer implements ServletContextListener {
|
||||
|
||||
private HazelcastInstance createHazelcastInstance() {
|
||||
Config config = new Config();
|
||||
|
||||
config.getNetworkConfig().setPort(getAvailablePort()).getJoin().getMulticastConfig().setEnabled(false);
|
||||
|
||||
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||
networkConfig.setPort(0);
|
||||
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||
config.getMapConfig(SESSION_MAP_NAME).setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
|
||||
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
private static int getAvailablePort() {
|
||||
try (ServerSocket socket = new ServerSocket(0)) {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user