Compare commits

...

75 Commits

Author SHA1 Message Date
Eleftheria Stein
478160e5b2 Add End-of-Life Notice 2021-07-19 12:22:21 +02:00
Eleftheria Stein
2ef91fa7a2 Remove from CI 2021-07-19 12:21:31 +02:00
Eleftheria Stein
8cd185db6b Update Deploy Docs host
The build conventions plugin does not support a property, so we must
override the configuration for docs.host to docs-ip.spring.io

Closes gh-1845
2021-04-28 17:58:00 +02:00
Eleftheria Stein
6fbd11e893 Update Deploy Docs host
Closes gh-1845
2021-04-28 14:42:36 +02:00
Eleftheria Stein
401bff267c Upgrade test dependencies 2021-04-28 14:22:29 +02:00
Eleftheria Stein
7805aff531 Next development version 2021-04-13 16:24:52 +02:00
Eleftheria Stein
7d5ba54289 Release 2.2.6.RELEASE 2021-04-13 15:59:32 +02:00
Eleftheria Stein
45e6c3a0ba Upgrade test dependencies 2021-04-13 15:14:43 +02:00
Eleftheria Stein
9f142ed49d Upgrade Spring Data to Moore-SR13
Closes gh-1839
2021-04-13 15:14:11 +02:00
Eleftheria Stein
0758dd737d Upgrade Spring Security to 5.2.10.RELEASE
Closes gh-1838
2021-04-13 15:13:30 +02:00
Eleftheria Stein
f7852f379a Upgrade Spring Framework to 5.2.14.RELEASE
Closes gh-1836
2021-04-13 15:13:03 +02:00
Eleftheria Stein
61d3d56e6d Upgrade Hazelcast to 3.12.12
Closes gh-1837
2021-04-13 15:12:08 +02:00
Eleftheria Stein
7d929457e4 Upgrade Reactor to Dysprosium-SR19
Closes gh-1835
2021-04-13 15:11:43 +02:00
Eleftheria Stein
3d266960ba Upgrade samples to Spring Boot 2.2.13.RELEASE
Closes gh-1840
2021-04-13 15:11:08 +02:00
Eleftheria Stein
4d96099e03 Throw exception if session created after response
Closes gh-1798
2021-03-25 13:01:09 +02:00
Eleftheria Stein
e0103f62d6 Update to spring-build-conventions:0.0.27.1.RELEASE
Fixes use of repo.spring.io
2021-01-28 18:14:51 +01:00
Eleftheria Stein
304b496caf Next development version 2021-01-19 17:48:59 +01:00
Eleftheria Stein
c1e3c2831d Release 2.2.5.RELEASE 2021-01-19 15:08:24 +01:00
Eleftheria Stein
e228279683 Upgrade Hazelcast to 3.12.11
Closes gh-1776
2021-01-18 16:28:54 +01:00
Eleftheria Stein
70c14368e5 Upgrade Spring Data to Moore-SR12
Closes gh-1775
2021-01-18 16:27:19 +01:00
Eleftheria Stein
7f9abc822f Upgrade Spring Security to 5.2.8
Closes gh-1774
2021-01-18 16:26:04 +01:00
Eleftheria Stein
f426f91574 Upgrade Spring Framework to 5.2.12
Closes gh-1773
2021-01-18 16:16:00 +01:00
Eleftheria Stein
316fe09f72 Upgrade Reactor to Dysprosium-SR16
Closes gh-1772
2021-01-18 15:46:38 +01:00
Eleftheria Stein
00338e23dd Update testcontainers to fix Docker connectivity
Closes gh-1751
2021-01-07 11:26:59 +01:00
Eleftheria Stein
c6c2d53204 Add artifactory credentials to build 2020-11-18 13:34:35 +01:00
Eleftheria Stein
bf5dcda905 Next development version 2020-09-16 16:37:49 +02:00
Eleftheria Stein
32ec8b2b28 Release 2.2.4.RELEASE 2020-09-16 12:32:33 +02:00
Eleftheria Stein
3268d1f790 Upgrade Spring Data to Moore-SR10
Closes gh-1701
2020-09-16 12:13:33 +02:00
Eleftheria Stein
380a1e81ac Upgrade test dependencies 2020-09-15 11:53:08 +02:00
Eleftheria Stein
5be4141103 Upgrade Spring Security to 5.2.6.RELEASE
Closes gh-1693
2020-09-15 11:51:36 +02:00
Eleftheria Stein
4ebf18dc4e Upgrade Spring Framework to 5.2.9.RELEASE
Closes gh-1692
2020-09-15 11:51:15 +02:00
Eleftheria Stein
b5c67736ad Upgrade Reactor to Dysprosium-SR12
Closes gh-1691
2020-09-15 11:50:23 +02:00
Eleftheria Stein
dfab409f30 Upgrade samples to Spring Boot 2.2.9.RELEASE
Closes gh-1690
2020-09-15 11:49:32 +02:00
Eleftheria Stein
a0a394d17f Remove JDK 9 and 10 from Jenkins build
Closes gh-1659
2020-07-16 15:53:36 +02:00
Eleftheria Stein
1a98f25fdb Next development version 2020-05-12 17:48:30 -04:00
Eleftheria Stein
1afb5d5a17 Release 2.2.3.RELEASE 2020-05-12 16:56:14 -04:00
Eleftheria Stein
365a244a9b Upgrade Spring Security to 5.2.4.RELEASE
Resolves gh-1634
2020-05-12 16:30:27 -04:00
Eleftheria Stein
0b4140d892 Upgrade Spring Data to Moore-SR7
Resolves gh-1633
2020-05-12 16:29:48 -04:00
Eleftheria Stein
78a85789c9 Upgrade Spring Framework to 5.2.6.RELEASE
Resolves gh-1632
2020-05-12 16:29:24 -04:00
Eleftheria Stein
59350ed559 Upgrade Reactor to Dysprosium-SR7
Resolves: gh-1631
2020-05-12 16:28:45 -04:00
Eleftheria Stein
811e156a9c Upgrade samples to Spring Boot 2.2.7
Resolves gh-1630
2020-05-12 16:28:08 -04:00
Eleftheria Stein
05a9903348 Upgrade test dependencies 2020-05-12 16:17:13 -04:00
Rob Winch
d8ae336b24 Find by Username Sample switch from DELETE to POST
Spring Boot 2.2 no longer adds HiddenHttpMethodFilter by default See
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes#httphiddenmethodfilter-disabled-by-default
This means that trying to map DELETE requests using _method variable
does not work.

This changes the mapping to use a POST which doesn't require the
HiddenHttpMethodFilter which might expose the application to unnecessary
security risk by allowing the HTTP method to be overridden.

Closes gh-1613
2020-04-13 09:44:15 -05:00
Eleftheria Stein
315112f2a2 Next Development Build 2020-03-04 16:43:52 -05:00
Eleftheria Stein
e859da6d27 Release 2.2.2.RELEASE 2020-03-04 16:03:14 -05:00
Eleftheria Stein
028bae1f11 Upgrade samples to Spring Boot 2.2.5
Resolves #1596
2020-03-04 10:50:54 -05:00
Eleftheria Stein
234cb6dd88 Upgrade Spring Security to 5.2.2.RELEASE
Resolves #1595
2020-03-04 10:50:05 -05:00
Eleftheria Stein
43101308ec Upgrade Spring Data to Moore-SR5
Resolves #1594
2020-03-04 10:49:17 -05:00
Eleftheria Stein
089f6b92de Upgrade Spring Framework to 5.2.4.RELEASE
Resolves #1593
2020-03-04 10:48:41 -05:00
Eleftheria Stein
c6d129a5a5 Upgrade Reactor to Dysprosium-SR5
Resolves #1592
2020-03-04 10:42:02 -05:00
Eleftheria Stein
938fd3c2e5 Next Development Build 2020-01-29 20:55:04 +01:00
Eleftheria Stein
45bb0f9b0c Run deployArtifacts before finalizeDeployArtifacts in build
This commit is needed to fix the release

Resolves: #1574
2020-01-29 16:39:07 +01:00
Eleftheria Stein
cddd84d564 Release 2.2.1.RELEASE 2020-01-28 10:57:10 +01:00
Eleftheria Stein
6931d40e6e Upgrade samples to Spring Boot 2.2.4.RELEASE
Resolves: #1563
2020-01-28 10:26:32 +01:00
Eleftheria Stein
3b672787f3 Upgrade Spring Data to Moore-SR4
Resolves: #1564
2020-01-28 10:22:14 +01:00
Eleftheria Stein
c0ee52b33b Upgrade Reactor to Dysprosium-SR4
Resolves: #1565
2020-01-28 10:21:32 +01:00
Eleftheria Stein
68f8641233 Upgrade Spring Security to 5.2.1.RELEASE
Resolves: #1566
2020-01-28 10:20:48 +01:00
Eleftheria Stein
e7b2af47e1 Upgrade Hazelcast to 3.12.5
Resolves: #1569
2020-01-28 10:19:16 +01:00
Eleftheria Stein
1ad6cbd7f8 Update note in custom-cookie index page
Resolves: gh-1559
2020-01-13 11:57:20 +01:00
Rob Winch
195af52d0b Upgrade to Spring Framework 5.2.2.RELEASE
Fixes gh-1548
2019-12-13 07:06:19 -06:00
Vedran Pavic
bc9d5f1299 Start building against Spring Framework 5.2.2.RELEASE snapshots
See: #1548
2019-11-16 10:20:11 +01:00
Vedran Pavic
3a4345eb6a Upgrade Gradle to 5.6.4 2019-11-15 22:23:23 +01:00
Vedran Pavic
6c41dea893 Polish contribution
Resolves: #1543
2019-11-15 22:10:35 +01:00
Eleftheria Stein
ee1d5b3b3c Document support for SameSite cookie directive
See: #1543
2019-11-15 22:06:13 +01:00
Christoph Dreis
89a4255679 Parse expression only once in PrincipalNameIndexResolver
Resolves: #1539
2019-11-15 12:13:42 +01:00
Rob Winch
6d2e51a0b9 Next Development Build 2019-10-15 15:32:01 -05:00
Rob Winch
798d398d9b Release 2.2.0.RELEASE 2019-10-15 15:31:21 -05:00
Vedran Pavic
085554f56b Polish DefaultCookieSerializer
See: #1514
2019-10-09 08:58:42 +02:00
Vedran Pavic
45b3b35db7 Update Travis CI config 2019-10-08 12:28:42 +02:00
Vedran Pavic
2d06e1159c Improve Hazelcast integration tests
Resolves: #1534
2019-10-08 12:04:42 +02:00
Vedran Pavic
927008bdc8 Ensure session cookie's expires directive uses GMT format
Resolves: #1514
2019-10-07 21:54:00 +02:00
Vedran Pavic
30588dc3c8 Improve Hazelcast client-server topology integration tests
Resolves: #1527
2019-10-06 15:54:02 +02:00
Vedran Pavic
2f79da00dc Upgrade Hazelcast to 3.12.3
Resolves: #1525
2019-10-06 11:41:03 +02:00
Vedran Pavic
e2abe36fa8 Upgrade samples to Spring Boot 2.2.0.RC1
Resolves: #1521
2019-10-03 17:16:59 +02:00
Vedran Pavic
456fd3adb4 Next development version 2019-10-01 06:26:41 +02:00
34 changed files with 261 additions and 645 deletions

View File

@@ -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

180
Jenkinsfile vendored
View File

@@ -1,180 +0,0 @@
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
pipelineTriggers([
cron('@daily')
]),
])
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
label 'spring-session'
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew clean check --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: check'
throw e
}
finally {
junit '**/build/test-results/*/*.xml'
}
}
}
}
},
jdk9: {
stage('JDK 9') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk9'}"]) {
sh './gradlew clean test --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk9'
throw e
}
}
}
}
},
jdk10: {
stage('JDK 10') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
sh './gradlew clean test --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
}
}
}
},
jdk11: {
stage('JDK 11') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
}
}
}
},
jdk12: {
stage('JDK 12') {
timeout(time: 45, unit: 'MINUTES') {
node('linux') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'openjdk12'}"]) {
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk12'
throw e
}
}
}
}
}
if (currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
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'
}
}
}
}
}
}
catch (e) {
currentBuild.result = 'FAILED: artifacts'
throw e
}
}
}
},
docs: {
stage('Deploy Docs') {
node('linux') {
checkout scm
sh "git clean -dfx"
try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
sh './gradlew deployDocs --no-daemon --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
}
catch (e) {
currentBuild.result = 'FAILED: docs'
throw e
}
}
}
}
}
}
finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if (buildNotSuccess || lastBuildNotSuccess) {
stage('Notify') {
node {
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
emailext(
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}
}
}

View File

@@ -1,3 +1,9 @@
[NOTE]
======
This branch of Spring Session has reached its https://github.com/spring-projects/spring-boot/wiki/Supported-Versions[End of Life], meaning that there are no further maintenance releases or security patches planned.
Please migrate to a supported branch as soon as possible.
======
= Spring Session
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]

View File

@@ -4,7 +4,7 @@ buildscript {
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.2.0.M6'
springBootVersion = '2.2.13.RELEASE'
}
repositories {
@@ -13,7 +13,7 @@ buildscript {
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.27.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.27.1.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}
@@ -34,3 +34,19 @@ subprojects {
useJUnitPlatform()
}
}
if (project.hasProperty('artifactoryUsername')) {
allprojects { project ->
project.repositories { repos ->
all { repo ->
if (!repo.url.toString().startsWith("https://repo.spring.io/")) {
return;
}
repo.credentials {
username = artifactoryUsername
password = artifactoryPassword
}
}
}
}
}

View File

@@ -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.2.7.BUILD-SNAPSHOT

View File

@@ -1,35 +1,35 @@
dependencyManagement {
imports {
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-RELEASE'
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-SR19'
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.14.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-SR13'
mavenBom 'org.springframework.security:spring-security-bom:5.2.10.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.15.3'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.12.2') {
dependencySet(group: 'com.hazelcast', version: '3.12.12') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.199'
dependency 'com.h2database:h2:1.4.200'
dependency 'com.ibm.db2:jcc:11.5.0.0'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.4.1.jre8'
dependency 'com.oracle.ojdbc:ojdbc8:19.3.0.0'
dependency 'com.zaxxer:HikariCP:3.4.1'
dependency 'com.zaxxer:HikariCP:3.4.5'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.2.0.RELEASE'
dependency 'io.lettuce:lettuce-core:5.2.2.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.17'
dependency 'mysql:mysql-connector-java:8.0.23'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.13.2'
dependency 'org.hsqldb:hsqldb:2.5.0'
dependency 'org.hsqldb:hsqldb:2.5.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.4'
dependency 'org.mockito:mockito-core:3.0.0'
dependency 'org.postgresql:postgresql:42.2.8'
dependency 'org.postgresql:postgresql:42.2.16'
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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()}.

View File

@@ -309,6 +309,10 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
if (!create) {
return null;
}
if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
&& this.response.isCommitted()) {
throw new IllegalStateException("Cannot create a session after the response has been committed");
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "

View File

@@ -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();
}

View File

@@ -62,6 +62,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -423,6 +424,18 @@ class SessionRepositoryFilterTests {
assertThat(this.response.getCookie("SESSION")).isNotNull();
}
@Test
void doFilterGetSessionNewWhenResponseCommittedThenException() {
assertThatIllegalStateException().isThrownBy(() -> doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse)
throws IOException {
wrappedResponse.getWriter().flush();
wrappedRequest.getSession();
}
}));
}
@Test
void doFilterGetSessionNew() throws Exception {
doFilter(new DoInFilter() {

View File

@@ -48,3 +48,9 @@ asciidoctor {
'version-release': releaseBuild,
'version-snapshot': snapshotBuild
}
remotes {
docs {
host = "docs-ip.spring.io"
}
}

View File

@@ -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].

View File

@@ -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`

View File

@@ -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>
}

View File

@@ -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.12")
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
"/opt/hazelcast/hazelcast.xml");
@BeforeAll
static void setUpClass() {

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -104,7 +104,7 @@ final class DatabaseContainers {
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.4.8");
super("mariadb:10.4.18");
}
@Override

View File

@@ -1,13 +1,6 @@
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'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.10.5.20201202'
}
dependencies {
@@ -16,15 +9,15 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
dependency 'org.slf4j:jcl-over-slf4j:1.7.28'
dependency 'org.slf4j:log4j-over-slf4j:1.7.28'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3'
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
dependency 'org.webjars:bootstrap:2.3.2'
dependency 'org.webjars:html5shiv:3.7.3'
dependency 'org.webjars:jquery:1.12.4'
dependency 'org.webjars:knockout:2.3.0'
dependency 'org.webjars:sockjs-client:0.3.4'
dependency 'org.webjars:stomp-websocket:2.3.0'
dependency 'org.webjars:stomp-websocket:2.3.4'
dependency 'org.webjars:webjars-taglib:0.3'
}
}

View File

@@ -53,6 +53,8 @@ class FindByUsernameTests {
private WebDriver driver;
private WebDriver driver2;
@BeforeEach
void setup() {
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
@@ -61,6 +63,9 @@ class FindByUsernameTests {
@AfterEach
void tearDown() {
this.driver.quit();
if (this.driver2 != null) {
this.driver2.quit();
}
}
@Test
@@ -79,6 +84,25 @@ class FindByUsernameTests {
home.terminateButtonDisabled();
}
@Test
void terminateOtherSession() throws Exception {
HomePage forgotToLogout = home(this.driver);
this.driver2 = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
HomePage terminateFogotSession = home(this.driver2);
terminateFogotSession.terminateSession(forgotToLogout.getSessionId()).assertAt();
LoginPage login = HomePage.go(this.driver);
login.assertAt();
}
private static HomePage home(WebDriver driver) {
LoginPage login = HomePage.go(driver);
HomePage home = login.form().login(HomePage.class);
home.assertAt();
return home;
}
@TestConfiguration
static class Config {

View File

@@ -56,6 +56,18 @@ public class HomePage extends BasePage {
}
public void terminateButtonDisabled() {
String sessionId = getSessionId();
WebElement element = getDriver().findElement(By.id("terminate-" + sessionId));
assertThat(element.isEnabled()).isFalse();
}
public HomePage terminateSession(String sessionId) {
WebElement terminate = getDriver().findElement(By.id("terminate-" + sessionId));
terminate.click();
return new HomePage(getDriver());
}
public String getSessionId() {
Set<Cookie> cookies = getDriver().manage().getCookies();
String cookieValue = null;
for (Cookie cookie : cookies) {
@@ -63,8 +75,7 @@ public class HomePage extends BasePage {
cookieValue = new String(Base64.getDecoder().decode(cookie.getValue()));
}
}
WebElement element = getDriver().findElement(By.id("terminate-" + cookieValue));
assertThat(element.isEnabled()).isFalse();
return cookieValue;
}
public HomePage logout() {

View File

@@ -26,8 +26,8 @@ import org.springframework.session.Session;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Controller for sending the user to the login view.
@@ -50,7 +50,7 @@ public class IndexController {
}
// end::findbyusername[]
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
@PostMapping("/sessions/{sessionIdToDelete}")
public String removeSession(Principal principal, @PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions.findByPrincipalName(principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {

View File

@@ -25,7 +25,7 @@
<td th:text="${#temporals.format(sessionElement.lastAccessedTime.atZone(T(java.time.ZoneId).systemDefault()),'dd/MMM/yyyy HH:mm:ss')}"></td>
<td th:text="${details?.accessType}"></td>
<td>
<form th:action="@{'/sessions/' + ${sessionElement.id}}" th:method="delete">
<form th:action="@{'/sessions/' + ${sessionElement.id}}" th:method="post">
<input th:id="'terminate-' + ${sessionElement.id}" type="submit" value="Terminate" th:disabled="${sessionElement.id == #httpSession.id}"/>
</form>
</td>

View File

@@ -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>

View File

@@ -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() {
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}