Compare commits

..

75 Commits

Author SHA1 Message Date
Rob Winch
357b4c8394 Release 2.0.9.RELEASE
Fixes: gh-1290
2019-01-10 21:39:58 -06:00
Rob Winch
a28bbdc997 Update to Spring Security 5.0.11.RELEASE
Fixes: gh-1293
2019-01-10 21:39:11 -06:00
Vedran Pavic
d6dc0f5cdd Update integration tests 2019-01-10 16:37:30 +01:00
Vedran Pavic
de521cd07c Upgrade test dependencies 2019-01-10 16:33:00 +01:00
Vedran Pavic
4662d5c303 Upgrade Spring Data to Kay-SR13
Resolves: #1289
2019-01-10 13:36:11 +01:00
Vedran Pavic
53de486da3 Upgrade Spring Framework to 5.0.11.RELEASE
Resolves: #1288
2019-01-09 16:23:14 +01:00
Vedran Pavic
d11e6ddfe9 Upgrade Reactor to Bismuth-SR15
Resolves: #1297
2019-01-08 21:47:35 +01:00
Vedran Pavic
6ed798e09f Fix Spring Security integration docs sample
Resolves: #1305
2019-01-08 21:46:19 +01:00
Vedran Pavic
adcfa87454 Polish 2018-12-26 20:17:14 +01:00
Vedran Pavic
c66fcf3f8b Fix assertion in Hazelcast tests 2018-12-24 21:00:23 +01:00
Vedran Pavic
ae6bc3e550 Upgrade samples to Spring Boot 2.0.7.RELEASE
Resolves: #1295
2018-12-17 21:36:21 +01:00
Roman Beskrovnyi
44f63cd301 Fix SessionRepositoryFilter Javadoc 2018-12-14 09:43:24 -06:00
Jeff
0a3dbfa43e Fix RedisOperationsSessionRepository javadoc typos
Resolves: #1275
2018-12-04 20:07:48 +01:00
Vedran Pavic
ae77a9db6c Next development version 2018-11-28 21:40:43 +01:00
Vedran Pavic
fe106ea7bb Release 2.0.8.RELEASE 2018-11-28 21:37:38 +01:00
Vedran Pavic
44ba9a97b7 Upgrade test dependencies 2018-11-28 19:23:01 +01:00
Vedran Pavic
0bdb106c30 Upgrade Spring Security to 5.0.10.RELEASE
Resolves: #1263
2018-11-28 19:22:31 +01:00
Vedran Pavic
43014247eb Upgrade Spring Data to Kay-SR12
Resolves: #1247
2018-11-27 15:11:13 +01:00
Vedran Pavic
7e8917ac47 Upgrade Spring Framework to 5.0.11.RELEASE
Resolves: #1246
2018-11-27 12:34:04 +01:00
Vedran Pavic
ad90867590 Polish 2018-11-26 19:10:13 +01:00
Vedran Pavic
32c28013f3 Make SessionUpdateEntryProcessor public
Resolves: #1265
2018-11-26 19:09:03 +01:00
Vedran Pavic
c5b43f096c Update integration tests 2018-11-26 19:07:20 +01:00
Vedran Pavic
82759642c3 Upgrade test dependencies 2018-11-26 18:59:40 +01:00
Vedran Pavic
74c5260754 Upgrade Reactor to Bismuth-SR14
Resolves: #1264
2018-11-23 19:35:01 +01:00
Vedran Pavic
55b4f6f017 Update integration tests 2018-11-02 23:15:25 +01:00
Vedran Pavic
9099bd5d3a Upgrade test dependencies 2018-11-02 23:15:13 +01:00
Vedran Pavic
afa1f0890e Upgrade samples to Spring Boot 2.0.6.RELEASE
Resolves: #1245
2018-11-02 22:59:28 +01:00
Vedran Pavic
ed3f6abf5d Polish contribution
Resolves: #1244
2018-11-02 22:51:14 +01:00
Josh Cummings
6c322631d4 Commit Session on Include Dispatch
The servlet spec disallows any writing of headers after an include has been issued.

This commit intercepts the include and commits the session, then
allowing the include to proceed.

See: #1244
2018-11-02 22:50:28 +01:00
Vedran Pavic
9575be9b7d Ensure HttpServletRequest#getRequestedSessionId API is respected
HttpSessionIdResolver supports resolving multiple requested session ids associated with the request - as a consequence, we need to validate the existence of requested session before returning the id. However, if no presented session ids do validate the null is returned, which violates the HttpServletRequest#getRequestedSessionId API.

This commit ensures that if no presented session ids are valid, we respect the HttpServletRequest#getRequestedSessionId API by returning first requested session id.

Resolves: #1236
2018-10-26 20:12:39 +02:00
Rob Winch
eae239febf Next Development Version 2018-10-15 20:12:43 -05:00
Rob Winch
b86b34ca2e Release 2.0.7.RELEASE 2018-10-15 20:11:03 -05:00
Rob Winch
c0a2220d3b Update to Spring Security 5.0.9.RELEASE
Fixes: gh-1226
2018-10-15 20:09:51 -05:00
Vedran Pavic
c9d6ef7f01 Upgrade integration tests 2018-10-15 19:59:54 +02:00
Vedran Pavic
c2c1311830 Upgrade test dependencies 2018-10-15 19:59:35 +02:00
Vedran Pavic
8c97a73b36 Upgrade Reactor to Bismuth-SR12
Resolves: #1225
2018-10-15 19:50:20 +02:00
Vedran Pavic
0886e237b6 Upgrade Spring Data to Kay-SR11
Resolves: #1224
2018-10-15 19:48:24 +02:00
Vedran Pavic
c57a286e35 Upgrade Spring Framework to 5.0.10.RELEASE
Resolves: #1209
2018-10-15 19:45:44 +02:00
Vedran Pavic
4c5f22900d Polish 2018-09-26 14:47:53 +02:00
Vedran Pavic
8a8f379b37 Disable network join in Hazelcast samples 2018-09-26 14:47:52 +02:00
Vedran Pavic
dc4a0ce61b Configure default LobHandler to use temporary LOBs on Oracle
JdbcOperationsSessionRepository recently introduced validation when inserting new session attributes in order to prevent data integrity violations in highly concurrent environments. This is done by using INSERT INTO ... SELECT statement to verify existence of session record in parent table. Such arrangement causes problems with Oracle if inserted attribute is of size 4 kb or more.

This commit enhances JdbcHttpSessionConfiguration to detect Oracle database is used, and set createTemporaryLob option on default LobHandler to true.

Resolves: #1208
2018-09-25 19:30:09 +02:00
Vedran Pavic
5944648c25 Fix SpringSessionRememberMeServices documentation example
Resolves: #1210
2018-09-25 19:27:39 +02:00
Vedran Pavic
4502724e8c Upgrade samples to Spring Boot 2.0.5.RELEASE
Closes gh-1194
2018-09-13 21:43:47 +02:00
Vedran Pavic
3c8cce652e Upgrade Gradle to 4.10.1 2018-09-13 21:43:11 +02:00
Vedran Pavic
425df2261f Update Jenkinsfile to specify node label 2018-09-13 18:24:10 +02:00
Vedran Pavic
7c6b143964 Ensure RedisHttpSessionConfiguration handles events for configured database
At present, RedisHttpSessionConfiguration doesn't take into account database index when handlng events. In situations where multiple apps use Spring Session with same Redis instance, but different database, this results in invalid session events.

This commits improves event handling in RedisHttpSessionConfiguration to ensure currently used database is considered.

Closes gh-1193
2018-09-13 18:23:59 +02:00
Vedran Pavic
39c640f456 Next development version 2018-09-11 00:21:34 +02:00
Vedran Pavic
f190da3757 Release 2.0.6.RELEASE 2018-09-11 00:19:56 +02:00
Vedran Pavic
34060cf0f6 Upgrade Spring Security to 5.0.8.RELEASE
Closes gh-1178
2018-09-10 23:00:13 +02:00
Vedran Pavic
ff10709f18 Polish
See gh-1185
2018-09-10 17:16:31 +02:00
Vedran Pavic
6e471f6441 Upgrade test dependencies 2018-09-10 17:16:12 +02:00
Vedran Pavic
a03be43450 Upgrade Spring Data to Kay-SR10
Closes gh-1186
2018-09-10 16:38:21 +02:00
Vedran Pavic
7e8f500df0 Verify session existence before update in ReactiveRedisOperationsSessionRepository
Currently, ReactiveRedisOperationsSessionRepository#save does not ensure session's existence before executing update. This can result in an invalid session record in Redis, since write use only delta, and in turn to error while retrieving the invalid session record.

This commit adds check for session existence if session is being updated.

Closes gh-1185
2018-09-10 00:04:06 +02:00
Vedran Pavic
b27742ce3e Upgrade Spring Framework to 5.0.9.RELEASE
Closes gh-1152
2018-09-07 18:09:27 +02:00
Vedran Pavic
af44e71af0 Update integration tests 2018-09-06 18:05:32 +02:00
Vedran Pavic
055c2bcb93 Upgrade test dependencies 2018-09-06 17:57:19 +02:00
Vedran Pavic
3335deb5d5 Upgrade Reactor to Bismuth-SR11
Closes gh-1180
2018-09-06 07:40:45 +02:00
Vedran Pavic
1079e9e016 Ignore failed rename operation for deleted session
Attempting to change session id for a deleted session currently results in "ERR no such key" error on rename operation of expired key. This commit addressed the problem by ignoring the aforementioned error.

Closes #1177
2018-09-04 23:18:07 +02:00
Vedran Pavic
5fa52be8d1 Upgrade Gradle to 4.10 2018-09-04 23:17:46 +02:00
Vedran Pavic
c90952031f Fix Jenkinsfile 2018-08-27 10:16:17 +02:00
Vedran Pavic
ce308ca513 Fix Jenkinsfile 2018-08-27 08:43:01 +02:00
Vedran Pavic
c8f78e510e Upgrade spring-build-conventions to 0.0.18.RELEASE 2018-08-24 23:59:12 +02:00
Rob Winch
748ba70a01 Fix settings.gradle on Windows
Fixes: gh-1169
2018-08-22 20:36:08 +02:00
Vedran Pavic
dc1c7cdf02 Polish 2018-08-13 08:36:23 +02:00
Vedran Pavic
3a972bef76 Insert new attributes conditionally in JDBC repo
At present, the insert of new attributes in JdbcOperationsSessionRepository is done unconditionally. This can cause data integrity violation errors with concurrent requests, where one request attempts to add new session attribute while the other, concurrent request, deletes the session.

This commit addresses the described scenario by executing insert of new attributes conditionally on presence of parent record.

Closes gh-1151
2018-08-13 08:36:08 +02:00
Vedran Pavic
6b7fc3af08 Fix Jenkinsfile 2018-08-01 11:01:44 +02:00
Vedran Pavic
535160bc92 Update Jenkinsfile
- set check stage timeout to 30 minutes
- set build discared to keep last 10 builds
- handle deploy stage errors
- general formatting improvements
2018-08-01 10:54:45 +02:00
Vedran Pavic
606e08007e Upgrade samples to Spring Boot 2.0.4.RELEASE
Closes gh-1138
2018-07-31 17:10:12 +02:00
Vedran Pavic
06fa33e48b Next development version 2018-07-29 09:54:16 +02:00
Vedran Pavic
d7c2e8e79c Release 2.0.5.RELEASE 2018-07-29 09:48:45 +02:00
Vedran Pavic
6bec95a298 Polish 2018-07-27 13:27:27 +02:00
Vedran Pavic
9249a140c9 Upgrade dependencies 2018-07-27 11:05:33 +02:00
Vedran Pavic
7f6dc801e0 Upgrade Spring Data to Kay-SR9
Closes gh-1122
2018-07-27 10:23:12 +02:00
Vedran Pavic
83d46ad685 Upgrade Spring Security to 5.0.7.RELEASE
Closes gh-1123
2018-07-27 01:14:21 +02:00
Vedran Pavic
21cef2b7fa Upgrade Spring Framework to 5.0.8.RELEASE
Closes gh-1121
2018-07-26 23:25:13 +02:00
74 changed files with 708 additions and 1108 deletions

93
Jenkinsfile vendored
View File

@@ -1,9 +1,9 @@
def projectProperties = [
[$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
pipelineTriggers([cron('@daily')])
]
properties(projectProperties)
properties([
buildDiscarder(logRotator(numToKeepStr: '10')),
pipelineTriggers([
cron('@daily')
]),
])
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
@@ -11,34 +11,61 @@ currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 30, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'
}
catch (e) {
currentBuild.result = 'FAILED: check'
throw e
}
finally {
junit '**/build/test-results/*/*.xml'
}
}
}
}
},
springio: {
stage('Spring IO') {
node {
checkout scm
try {
sh "./gradlew clean check --refresh-dependencies --no-daemon"
} catch(Exception e) {
currentBuild.result = 'FAILED: check'
sh "./gradlew clean springIoCheck --stacktrace --no-daemon --refresh-dependencies -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**'"
}
catch(e) {
currentBuild.result = 'FAILED: springio'
throw e
} finally {
junit '**/build/*-results/*.xml'
}
finally {
junit '**/build/spring-io*-results/*.xml'
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
if (currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node {
checkout scm
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')]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -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 --refresh-dependencies --no-daemon --stacktrace"
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')]) {
sh './gradlew deployArtifacts finalizeDeployArtifacts --stacktrace --no-daemon --refresh-dependencies -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
}
}
}
},
@@ -46,32 +73,38 @@ try {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
try {
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh './gradlew deployDocs --stacktrace --no-daemon --refresh-dependencies -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
}
}
catch (e) {
currentBuild.result = 'FAILED: docs'
throw e
}
}
}
}
}
} finally {
}
finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if(buildNotSuccess || lastBuildNotSuccess) {
stage('Notifiy') {
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}"""
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"
emailext(
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}

View File

@@ -1,6 +1,6 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.17.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {

View File

@@ -24,7 +24,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
@@ -54,7 +53,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
// tag::rememberme-bean[]
@Bean
RememberMeServices rememberMeServices() {
public SpringSessionRememberMeServices rememberMeServices() {
SpringSessionRememberMeServices rememberMeServices =
new SpringSessionRememberMeServices();
// optionally customize

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -16,18 +16,16 @@
package docs.security;
import java.net.HttpCookie;
import java.time.Duration;
import java.util.Base64;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
@@ -45,7 +43,6 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@@ -81,7 +78,7 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.andReturn();
// @formatter:on
HttpCookie cookie = getSessionCookie(result.getResponse());
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
@@ -89,15 +86,5 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
private HttpCookie getSessionCookie(HttpServletResponse response) {
for (HttpCookie cookie : HttpCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE))) {
if ("SESSION".equals(cookie.getName())) {
return cookie;
}
}
return null;
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -16,18 +16,16 @@
package docs.security;
import java.net.HttpCookie;
import java.time.Duration;
import java.util.Base64;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
@@ -45,7 +43,6 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -81,7 +78,7 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.andReturn();
// @formatter:on
HttpCookie cookie = getSessionCookie(result.getResponse());
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
@@ -89,15 +86,5 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
private HttpCookie getSessionCookie(HttpServletResponse response) {
for (HttpCookie cookie : HttpCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE))) {
if ("SESSION".equals(cookie.getName())) {
return cookie;
}
}
return null;
}
}
// end::class[]

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* 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.
@@ -30,10 +30,11 @@ import org.springframework.session.security.SpringSessionBackedSessionRegistry;
*/
// tag::class[]
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public class SecurityConfiguration<S extends Session>
extends WebSecurityConfigurerAdapter {
@Autowired
private FindByIndexNameSessionRepository<Session> sessionRepository;
private FindByIndexNameSessionRepository<S> sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
@@ -47,7 +48,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}
@Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
}
}

View File

@@ -4,7 +4,7 @@
<module name="Checker">
<!-- Supressions -->
<module name="SuppressionFilter">
<property name="file" value="${configDir}/suppressions.xml"/>
<property name="file" value="${config_loc}/suppressions.xml"/>
</module>
<!-- Root Checks -->

View File

@@ -1,16 +0,0 @@
^\Q/*\E$
^\Q * Copyright 2014-\E20\d\d\Q the original author or authors.\E$
^\Q *\E$
^\Q * Licensed under the Apache License, Version 2.0 (the "License");\E$
^\Q * you may not use this file except in compliance with the License.\E$
^\Q * You may obtain a copy of the License at\E$
^\Q *\E$
^\Q * http://www.apache.org/licenses/LICENSE-2.0\E$
^\Q *\E$
^\Q * Unless required by applicable law or agreed to in writing, software\E$
^\Q * distributed under the License is distributed on an "AS IS" BASIS,\E$
^\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\E$
^\Q * See the License for the specific language governing permissions and\E$
^\Q * limitations under the License.\E$
^\Q */\E$
^.*$

View File

@@ -7,7 +7,6 @@
<!-- docs -->
<suppress files="[\\/]docs[\\/]" checks="Javadoc*"/>
<suppress files="[\\/]docs[\\/]" checks="AvoidStaticImport"/>
<suppress files="[\\/]docs[\\/]" checks="InnerTypeLast"/>
<!-- samples -->

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.3.RELEASE
version=2.1.0.M1
springBootVersion=2.0.7.RELEASE
version=2.0.9.RELEASE

View File

@@ -1,31 +1,32 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Californium-M1'
mavenBom 'org.springframework:spring-framework-bom:5.1.0.RC1'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-RC1'
mavenBom 'org.springframework.security:spring-security-bom:5.1.0.M2'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.1'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR15'
mavenBom 'org.springframework:spring-framework-bom:5.0.12.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR13'
mavenBom 'org.springframework.security:spring-security-bom:5.0.11.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.10.5'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.10.3') {
dependencySet(group: 'com.hazelcast', version: '3.9.4') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependency 'com.h2database:h2:1.4.197'
dependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8'
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.1.0.M1'
dependency 'io.lettuce:lettuce-core:5.1.3.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.11'
dependency 'mysql:mysql-connector-java:8.0.13'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.10.0'
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.6'
dependency 'org.mockito:mockito-core:2.20.1'
dependency 'org.postgresql:postgresql:42.2.4'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.3.0'
dependency 'org.mockito:mockito-core:2.23.4'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -16,20 +16,6 @@
package sample;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,11 +30,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
@@ -63,7 +46,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class FindByUsernameTests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;
@@ -113,62 +96,6 @@ public class FindByUsernameTests {
redisContainer().getFirstMappedPort());
}
@Bean
public FilterRegistrationBean<SetCookieHandlerFilter> testFilter() {
FilterRegistrationBean<SetCookieHandlerFilter> registrationBean = new FilterRegistrationBean<>(
new SetCookieHandlerFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
private static class SetCookieHandlerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(
httpServletResponse) {
@Override
public void addHeader(String name, String value) {
if (HttpHeaders.SET_COOKIE.equals(name)) {
List<HttpCookie> cookies = HttpCookie.parse(value);
if (!cookies.isEmpty()) {
addCookie(toServletCookie(cookies.get(0)));
}
}
super.setHeader(name, value);
}
};
chain.doFilter(request, responseWrapper);
}
@Override
public void destroy() {
}
private static Cookie toServletCookie(HttpCookie httpCookie) {
Cookie cookie = new Cookie(httpCookie.getName(), httpCookie.getValue());
String domain = httpCookie.getDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge((int) httpCookie.getMaxAge());
cookie.setPath(httpCookie.getPath());
cookie.setSecure(httpCookie.getSecure());
cookie.setHttpOnly(httpCookie.isHttpOnly());
return cookie;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -16,20 +16,6 @@
package sample;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -42,18 +28,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
/**
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@@ -99,65 +79,4 @@ public class BootTests {
login.assertAt();
}
@TestConfiguration
static class Config {
@Bean
public FilterRegistrationBean<SetCookieHandlerFilter> testFilter() {
FilterRegistrationBean<SetCookieHandlerFilter> registrationBean = new FilterRegistrationBean<>(
new SetCookieHandlerFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
private static class SetCookieHandlerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(
httpServletResponse) {
@Override
public void addHeader(String name, String value) {
if (HttpHeaders.SET_COOKIE.equals(name)) {
List<HttpCookie> cookies = HttpCookie.parse(value);
if (!cookies.isEmpty()) {
addCookie(toServletCookie(cookies.get(0)));
}
}
super.setHeader(name, value);
}
};
chain.doFilter(request, responseWrapper);
}
@Override
public void destroy() {
}
private static Cookie toServletCookie(HttpCookie httpCookie) {
Cookie cookie = new Cookie(httpCookie.getName(), httpCookie.getValue());
String domain = httpCookie.getDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge((int) httpCookie.getMaxAge());
cookie.setPath(httpCookie.getPath());
cookie.setSecure(httpCookie.getSecure());
cookie.setHttpOnly(httpCookie.isHttpOnly());
return cookie;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -16,20 +16,8 @@
package sample;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -45,11 +33,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
@@ -65,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@AutoConfigureMockMvc
public class HttpRedisJsonTest {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;
@@ -135,62 +120,6 @@ public class HttpRedisJsonTest {
redisContainer().getFirstMappedPort());
}
@Bean
public FilterRegistrationBean<SetCookieHandlerFilter> testFilter() {
FilterRegistrationBean<SetCookieHandlerFilter> registrationBean = new FilterRegistrationBean<>(
new SetCookieHandlerFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
private static class SetCookieHandlerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(
httpServletResponse) {
@Override
public void addHeader(String name, String value) {
if (HttpHeaders.SET_COOKIE.equals(name)) {
List<HttpCookie> cookies = HttpCookie.parse(value);
if (!cookies.isEmpty()) {
addCookie(toServletCookie(cookies.get(0)));
}
}
super.setHeader(name, value);
}
};
chain.doFilter(request, responseWrapper);
}
@Override
public void destroy() {
}
private static Cookie toServletCookie(HttpCookie httpCookie) {
Cookie cookie = new Cookie(httpCookie.getName(), httpCookie.getValue());
String domain = httpCookie.getDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge((int) httpCookie.getMaxAge());
cookie.setPath(httpCookie.getPath());
cookie.setSecure(httpCookie.getSecure());
cookie.setHttpOnly(httpCookie.isHttpOnly());
return cookie;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class RedisSerializerTest {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@SpringSessionRedisOperations
private RedisTemplate<Object, Object> sessionRedisTemplate;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -16,20 +16,6 @@
package sample;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,11 +30,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
@@ -62,7 +45,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class BootTests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private MockMvc mockMvc;
@@ -119,62 +102,6 @@ public class BootTests {
redisContainer().getFirstMappedPort());
}
@Bean
public FilterRegistrationBean<SetCookieHandlerFilter> testFilter() {
FilterRegistrationBean<SetCookieHandlerFilter> registrationBean = new FilterRegistrationBean<>(
new SetCookieHandlerFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
private static class SetCookieHandlerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(
httpServletResponse) {
@Override
public void addHeader(String name, String value) {
if (HttpHeaders.SET_COOKIE.equals(name)) {
List<HttpCookie> cookies = HttpCookie.parse(value);
if (!cookies.isEmpty()) {
addCookie(toServletCookie(cookies.get(0)));
}
}
super.setHeader(name, value);
}
};
chain.doFilter(request, responseWrapper);
}
@Override
public void destroy() {
}
private static Cookie toServletCookie(HttpCookie httpCookie) {
Cookie cookie = new Cookie(httpCookie.getName(), httpCookie.getValue());
String domain = httpCookie.getDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge((int) httpCookie.getMaxAge());
cookie.setPath(httpCookie.getPath());
cookie.setSecure(httpCookie.getSecure());
cookie.setHttpOnly(httpCookie.isHttpOnly());
return cookie;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class AttributeTests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@LocalServerPort
private int port;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Value("${local.server.port}")
private String port;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -36,7 +36,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public Form form() {
@@ -51,7 +51,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(name = "submit")
private WebElement button;
public Form(SearchContext context) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -42,7 +42,8 @@ public class SessionConfig {
int port = SocketUtils.findAvailableTcpPort();
config.getNetworkConfig()
.setPort(port);
.setPort(port)
.getJoin().getMulticastConfig().setEnabled(false);
System.out.println("Hazelcast port #: " + port);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebAppConfiguration
public class RestMockMvcTests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -34,7 +34,7 @@ public class LoginPage extends BasePage {
@FindBy(name = "password")
private WebElement password;
@FindBy(tagName = "button")
@FindBy(css = "input[type='submit']")
private WebElement button;
public LoginPage(WebDriver driver) {
@@ -47,7 +47,7 @@ public class LoginPage extends BasePage {
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public HomePage login(String user, String password) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -64,7 +64,9 @@ public class Initializer implements ServletContextListener {
private HazelcastInstance createHazelcastInstance() {
Config config = new Config();
config.getNetworkConfig().setPort(getAvailablePort());
config.getNetworkConfig()
.setPort(getAvailablePort())
.getJoin().getMulticastConfig().setEnabled(false);
config.getMapConfig(SESSION_MAP_NAME)
.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
@Bean
public GenericContainer redisContainer() {

View File

@@ -10,7 +10,7 @@ String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { File buildFile ->
if (buildFile.name == 'build.gradle') {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replaceAll(File.separator, ':')
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
include projectPath
}
else {

View File

@@ -132,6 +132,10 @@ public final class MapSession implements Session, Serializable {
return this.originalId;
}
void setOriginalId(String originalId) {
this.originalId = originalId;
}
@Override
public String changeSessionId() {
String changedId = generateId();

View File

@@ -73,6 +73,7 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
}

View File

@@ -76,6 +76,7 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
return Mono.fromRunnable(() -> {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
session.setOriginalId(session.getId());
}
this.sessions.put(session.getId(), new MapSession(session));
});

View File

@@ -81,7 +81,7 @@ public interface Session {
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
T result = getAttribute(name);
return (result != null ? result : defaultValue);
return (result != null) ? result : defaultValue;
}
/**

View File

@@ -110,8 +110,9 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
@PostConstruct
public void init() {
CookieSerializer cookieSerializer = (this.cookieSerializer != null
? this.cookieSerializer : createDefaultCookieSerializer());
CookieSerializer cookieSerializer = (this.cookieSerializer != null)
? this.cookieSerializer
: createDefaultCookieSerializer();
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
}

View File

@@ -16,13 +16,8 @@
package org.springframework.session.web.http;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.BitSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -46,22 +41,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
private static final BitSet domainValid = new BitSet(128);
static {
for (char c = '0'; c <= '9'; c++) {
domainValid.set(c);
}
for (char c = 'a'; c <= 'z'; c++) {
domainValid.set(c);
}
for (char c = 'A'; c <= 'Z'; c++) {
domainValid.set(c);
}
domainValid.set('.');
domainValid.set('-');
}
private String cookieName = "SESSION";
private Boolean useSecureCookie;
@@ -82,8 +61,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
private String rememberMeRequestAttribute;
private String sameSite = "Lax";
/*
* (non-Javadoc)
*
@@ -98,8 +75,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding
? base64Decode(cookie.getValue())
: cookie.getValue());
? base64Decode(cookie.getValue()) : cookie.getValue());
if (sessionId == null) {
continue;
}
@@ -125,43 +101,38 @@ public class DefaultCookieSerializer implements CookieSerializer {
HttpServletRequest request = cookieValue.getRequest();
HttpServletResponse response = cookieValue.getResponse();
StringBuilder sb = new StringBuilder();
sb.append(this.cookieName).append('=');
String value = getValue(cookieValue);
if (value != null && value.length() > 0) {
validateValue(value);
sb.append(value);
}
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));
sb.append("; Expires=")
.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
}
String domain = getDomainName(request);
if (domain != null && domain.length() > 0) {
validateDomain(domain);
sb.append("; Domain=").append(domain);
}
String path = getCookiePath(request);
if (path != null && path.length() > 0) {
validatePath(path);
sb.append("; Path=").append(path);
}
if (isSecureCookie(request)) {
sb.append("; Secure");
}
if (this.useHttpOnlyCookie) {
sb.append("; HttpOnly");
}
if (this.sameSite != null) {
sb.append("; SameSite=").append(this.sameSite);
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = (this.jvmRoute != null)
? requestedCookieValue + this.jvmRoute
: requestedCookieValue;
Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
? base64Encode(actualCookieValue) : actualCookieValue);
sessionCookie.setSecure(isSecureCookie(request));
sessionCookie.setPath(getCookiePath(request));
String domainName = getDomainName(request);
if (domainName != null) {
sessionCookie.setDomain(domainName);
}
response.addHeader("Set-Cookie", sb.toString());
if (this.useHttpOnlyCookie) {
sessionCookie.setHttpOnly(true);
}
if (cookieValue.getCookieMaxAge() < 0) {
if (this.rememberMeRequestAttribute != null
&& request.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
sessionCookie.setMaxAge(cookieValue.getCookieMaxAge());
response.addCookie(sessionCookie);
}
/**
@@ -192,81 +163,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
return new String(encodedCookieBytes);
}
private String getValue(CookieValue cookieValue) {
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = requestedCookieValue;
if (this.jvmRoute != null) {
actualCookieValue = requestedCookieValue + this.jvmRoute;
}
if (this.useBase64Encoding) {
actualCookieValue = base64Encode(actualCookieValue);
}
return actualCookieValue;
}
private void validateValue(String value) {
int start = 0;
int end = value.length();
if ((end > 1) && (value.charAt(0) == '"') && (value.charAt(end - 1) == '"')) {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c
|| c == 0x7f) {
throw new IllegalArgumentException(
"Invalid character in cookie value: " + Integer.toString(c));
}
}
}
private int getMaxAge(CookieValue cookieValue) {
int maxAge = cookieValue.getCookieMaxAge();
if (maxAge < 0) {
if (this.rememberMeRequestAttribute != null && cookieValue.getRequest()
.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is
// enabled
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
}
else if (this.cookieMaxAge != null) {
cookieValue.setCookieMaxAge(this.cookieMaxAge);
}
}
return cookieValue.getCookieMaxAge();
}
private void validateDomain(String domain) {
int i = 0;
int cur = -1;
int prev;
char[] chars = domain.toCharArray();
while (i < chars.length) {
prev = cur;
cur = chars[i];
if (!domainValid.get(cur)
|| ((prev == '.' || prev == -1) && (cur == '.' || cur == '-'))
|| (prev == '-' && cur == '.')) {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
i++;
}
if (cur == '.' || cur == '-') {
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
}
}
private void validatePath(String path) {
for (char ch : path.toCharArray()) {
if (ch < 0x20 || ch > 0x7E || ch == ';') {
throw new IllegalArgumentException("Invalid cookie path: " + path);
}
}
}
/**
* Sets if a Cookie marked as secure should be used. The default is to use the value
* of {@link HttpServletRequest#isSecure()}.
@@ -422,16 +318,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
this.rememberMeRequestAttribute = rememberMeRequestAttribute;
}
/**
* Set the value for the {@code SameSite} cookie directive. The default value is
* {@code Lax}.
* @param sameSite the SameSite directive value
* @since 2.1.0
*/
public void setSameSite(String sameSite) {
this.sameSite = sameSite;
}
private String getDomainName(HttpServletRequest request) {
if (this.domainName != null) {
return this.domainName;

View File

@@ -98,8 +98,8 @@ public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
return (headerValue != null ? Collections.singletonList(headerValue)
: Collections.emptyList());
return (headerValue != null) ? Collections.singletonList(headerValue)
: Collections.emptyList();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -24,13 +24,8 @@ import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.session.Session;
/**
@@ -38,14 +33,11 @@ import org.springframework.session.Session;
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @author Vedran Pavic
* @since 1.1
*/
@SuppressWarnings("deprecation")
class HttpSessionAdapter<S extends Session> implements HttpSession {
private static final Log logger = LogFactory.getLog(HttpSessionAdapter.class);
private S session;
private final ServletContext servletContext;
@@ -137,28 +129,7 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void setAttribute(String name, Object value) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue).valueUnbound(
new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
if (value instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) value)
.valueBound(new HttpSessionBindingEvent(this, name, value));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
}
@Override
@@ -169,17 +140,7 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
@Override
public void removeAttribute(String name) {
checkState();
Object oldValue = this.session.getAttribute(name);
this.session.removeAttribute(name);
if (oldValue instanceof HttpSessionBindingListener) {
try {
((HttpSessionBindingListener) oldValue)
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
catch (Throwable th) {
logger.error("Error invoking session binding event listener", th);
}
}
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
* @since 2.0.0
*/
public interface HttpSessionIdResolver {

View File

@@ -174,11 +174,11 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
private void trackContentLength(byte[] content) {
checkContentLength(content != null ? content.length : 0);
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(char[] content) {
checkContentLength(content != null ? content.length : 0);
checkContentLength((content != null) ? content.length : 0);
}
private void trackContentLength(int content) {
@@ -257,13 +257,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
@Override
public int hashCode() {
return this.delegate.hashCode();
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
public int hashCode() {
return this.delegate.hashCode();
}
@Override
@@ -502,13 +502,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
}
@Override
public int hashCode() {
return this.delegate.hashCode();
public boolean equals(Object obj) {
return this.delegate.equals(obj);
}
@Override
public boolean equals(Object obj) {
return this.delegate.equals(obj);
public int hashCode() {
return this.delegate.hashCode();
}
@Override

View File

@@ -21,8 +21,11 @@ import java.time.Instant;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@@ -46,7 +49,7 @@ import org.springframework.session.SessionRepository;
* {@link org.springframework.session.SessionRepository}.
*
* The {@link SessionRepositoryFilter} uses a {@link HttpSessionIdResolver} (default
* {@link CookieHttpSessionIdResolver} to bridge logic between an
* {@link CookieHttpSessionIdResolver}) to bridge logic between an
* {@link javax.servlet.http.HttpSession} and the
* {@link org.springframework.session.Session} abstraction. Specifically:
*
@@ -71,6 +74,7 @@ import org.springframework.session.SessionRepository;
* @since 1.0
* @author Rob Winch
* @author Vedran Pavic
* @author Josh Cummings
*/
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
@@ -205,6 +209,8 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
private boolean requestedSessionCached;
private String requestedSessionId;
private Boolean requestedSessionIdValid;
private boolean requestedSessionInvalidated;
@@ -277,7 +283,6 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
}
return isRequestedSessionIdValid(requestedSession);
}
return this.requestedSessionIdValid;
}
@@ -351,8 +356,16 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
@Override
public String getRequestedSessionId() {
S requestedSession = getRequestedSession();
return (requestedSession != null ? requestedSession.getId() : null);
if (this.requestedSessionId == null) {
getRequestedSession();
}
return this.requestedSessionId;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
return new SessionCommittingRequestDispatcher(requestDispatcher);
}
private S getRequestedSession() {
@@ -360,10 +373,14 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
.resolveSessionIds(this);
for (String sessionId : sessionIds) {
if (this.requestedSessionId == null) {
this.requestedSessionId = sessionId;
}
S session = SessionRepositoryFilter.this.sessionRepository
.findById(sessionId);
if (session != null) {
this.requestedSession = session;
this.requestedSessionId = sessionId;
break;
}
}
@@ -375,6 +392,7 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
private void clearRequestedSessionCache() {
this.requestedSessionCached = false;
this.requestedSession = null;
this.requestedSessionId = null;
}
/**
@@ -399,6 +417,35 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
}
}
/**
* Ensures session is committed before issuing an include.
*
* @since 1.3.4
*/
private final class SessionCommittingRequestDispatcher
implements RequestDispatcher {
private final RequestDispatcher delegate;
SessionCommittingRequestDispatcher(RequestDispatcher delegate) {
this.delegate = delegate;
}
@Override
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
this.delegate.forward(request, response);
}
@Override
public void include(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
SessionRepositoryRequestWrapper.this.commitSession();
this.delegate.include(request, response);
}
}
}
}

View File

@@ -71,9 +71,9 @@ public final class WebSocketRegistryListener
SessionDisconnectEvent e = (SessionDisconnectEvent) event;
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor
.getSessionAttributes(e.getMessage().getHeaders());
String httpSessionId = (sessionAttributes != null
String httpSessionId = (sessionAttributes != null)
? SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes)
: null);
: null;
afterConnectionClosed(httpSessionId, e.getSessionId());
}
}

View File

@@ -117,8 +117,9 @@ public final class SessionRepositoryMessageInterceptor<S extends Session>
}
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor
.getSessionAttributes(message.getHeaders());
String sessionId = (sessionHeaders != null
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME) : null);
String sessionId = (sessionHeaders != null)
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME)
: null;
if (sessionId != null) {
S session = this.sessionRepository.findById(sessionId);
if (session != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2017 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.
@@ -18,14 +18,12 @@ package org.springframework.session.web.http;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.ResponseCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.MapSession;
@@ -85,7 +83,7 @@ public class CookieHttpSessionIdResolverTests {
this.strategy.setSessionId(this.request, this.response, this.session.getId());
this.strategy.setSessionId(this.request, this.response, this.session.getId());
assertThat(this.response.getHeaders("Set-Cookie")).hasSize(1);
assertThat(this.response.getCookies()).hasSize(1);
}
@Test
@@ -95,12 +93,11 @@ public class CookieHttpSessionIdResolverTests {
this.strategy.setSessionId(this.request, this.response, this.session.getId());
this.strategy.setSessionId(this.request, this.response, newSession.getId());
List<ResponseCookie> cookies = ResponseCookieParser.parse(this.response);
Cookie[] cookies = this.response.getCookies();
assertThat(cookies).hasSize(2);
assertThat(base64Decode(cookies.get(0).getValue()))
.isEqualTo(this.session.getId());
assertThat(base64Decode(cookies.get(1).getValue())).isEqualTo(newSession.getId());
assertThat(base64Decode(cookies[0].getValue())).isEqualTo(this.session.getId());
assertThat(base64Decode(cookies[1].getValue())).isEqualTo(newSession.getId());
}
@Test
@@ -108,7 +105,7 @@ public class CookieHttpSessionIdResolverTests {
this.request.setContextPath("/somethingunique");
this.strategy.setSessionId(this.request, this.response, this.session.getId());
ResponseCookie sessionCookie = getCookie();
Cookie sessionCookie = this.response.getCookie(this.cookieName);
assertThat(sessionCookie.getPath())
.isEqualTo(this.request.getContextPath() + "/");
}
@@ -131,7 +128,7 @@ public class CookieHttpSessionIdResolverTests {
this.request.setContextPath("/somethingunique");
this.strategy.expireSession(this.request, this.response);
ResponseCookie sessionCookie = getCookie();
Cookie sessionCookie = this.response.getCookie(this.cookieName);
assertThat(sessionCookie.getPath())
.isEqualTo(this.request.getContextPath() + "/");
}
@@ -176,12 +173,8 @@ public class CookieHttpSessionIdResolverTests {
this.request.setCookies(new Cookie(this.cookieName, base64Encode(value)));
}
private ResponseCookie getCookie() {
return ResponseCookieParser.parse(this.response, this.cookieName);
}
private String getSessionId() {
return base64Decode(getCookie().getValue());
return base64Decode(this.response.getCookie(this.cookieName).getValue());
}
private static String base64Encode(String value) {

View File

@@ -26,7 +26,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.ResponseCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
@@ -325,7 +324,7 @@ public class DefaultCookieSerializerTests {
public void writeCookieCookieMaxAgeDefault() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(-1);
assertThat(getCookie().getMaxAge()).isEqualTo(-1);
}
@Test
@@ -334,7 +333,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(100);
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
@Test
@@ -343,7 +342,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(""));
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(0);
assertThat(getCookie().getMaxAge()).isEqualTo(0);
}
@Test
@@ -353,7 +352,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue);
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(100);
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
// --- secure ---
@@ -362,7 +361,7 @@ public class DefaultCookieSerializerTests {
public void writeCookieDefaultInsecureRequest() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isSecure()).isFalse();
assertThat(getCookie().getSecure()).isFalse();
}
@Test
@@ -372,7 +371,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isSecure()).isTrue();
assertThat(getCookie().getSecure()).isTrue();
}
@Test
@@ -381,7 +380,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isSecure()).isTrue();
assertThat(getCookie().getSecure()).isTrue();
}
@Test
@@ -391,7 +390,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isSecure()).isFalse();
assertThat(getCookie().getSecure()).isFalse();
}
@Test
@@ -400,7 +399,7 @@ public class DefaultCookieSerializerTests {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().isSecure()).isFalse();
assertThat(getCookie().getSecure()).isFalse();
}
// --- jvmRoute ---
@@ -453,7 +452,7 @@ public class DefaultCookieSerializerTests {
this.serializer.setRememberMeRequestAttribute("rememberMe");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(Integer.MAX_VALUE);
assertThat(getCookie().getMaxAge()).isEqualTo(Integer.MAX_VALUE);
}
@Test
@@ -464,40 +463,7 @@ public class DefaultCookieSerializerTests {
cookieValue.setCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue);
assertThat(getCookie().getMaxAge().getSeconds()).isEqualTo(100);
}
// --- sameSite ---
@Test
public void writeCookieDefaultSameSiteLax() {
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteLax() {
this.serializer.setSameSite("Lax");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
}
@Test
public void writeCookieSetSameSiteStrict() {
this.serializer.setSameSite("Strict");
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isEqualTo("Strict");
}
@Test
public void writeCookieSetSameSiteNull() {
this.serializer.setSameSite(null);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getSameSite()).isNull();
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
public void setCookieName(String cookieName) {
@@ -512,8 +478,8 @@ public class DefaultCookieSerializerTests {
return new Cookie(name, value);
}
private ResponseCookie getCookie() {
return ResponseCookieParser.parse(this.response, this.cookieName);
private Cookie getCookie() {
return this.response.getCookie(this.cookieName);
}
private String getCookieValue() {
@@ -521,6 +487,9 @@ public class DefaultCookieSerializerTests {
if (!this.useBase64Encoding) {
return value;
}
if (value == null) {
return null;
}
return new String(Base64.getDecoder().decode(value));
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2014-2018 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
*
* http://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.web.http;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.lang.NonNull;
final class ResponseCookieParser {
private ResponseCookieParser() {
}
static List<ResponseCookie> parse(HttpServletResponse response) {
return doParse(response, null);
}
static ResponseCookie parse(HttpServletResponse response, String cookieName) {
List<ResponseCookie> responseCookies = doParse(response, cookieName);
return (!responseCookies.isEmpty() ? responseCookies.get(0) : null);
}
@NonNull
private static List<ResponseCookie> doParse(HttpServletResponse response,
String cookieName) {
List<ResponseCookie> responseCookies = new ArrayList<>();
for (String setCookieHeader : response.getHeaders(HttpHeaders.SET_COOKIE)) {
String[] cookieParts = setCookieHeader.split("\\s*=\\s*", 2);
if (cookieParts.length != 2) {
return null;
}
String name = cookieParts[0];
if (cookieName != null && !name.equals(cookieName)) {
continue;
}
String[] valueAndDirectives = cookieParts[1].split("\\s*;\\s*", 2);
String value = valueAndDirectives[0];
String[] directives = valueAndDirectives[1].split("\\s*;\\s*");
String domain = null;
int maxAge = -1;
String path = null;
boolean secure = false;
boolean httpOnly = false;
String sameSite = null;
for (String directive : directives) {
if (directive.startsWith("Domain")) {
domain = directive.split("=")[1];
}
if (directive.startsWith("Max-Age")) {
maxAge = Integer.parseInt(directive.split("=")[1]);
}
if (directive.startsWith("Path")) {
path = directive.split("=")[1];
}
if (directive.startsWith("Secure")) {
secure = true;
}
if (directive.startsWith("HttpOnly")) {
httpOnly = true;
}
if (directive.startsWith("SameSite")) {
sameSite = directive.split("=")[1];
}
}
responseCookies.add(ResponseCookie.from(name, value).maxAge(maxAge).path(path)
.domain(domain).secure(secure).httpOnly(httpOnly).sameSite(sameSite)
.build());
}
return responseCookies;
}
}

View File

@@ -27,8 +27,6 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
@@ -38,8 +36,6 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.assertj.core.data.Offset;
@@ -51,7 +47,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.ResponseCookie;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -424,7 +419,7 @@ public class SessionRepositoryFilterTests {
}
});
assertThat(getSessionCookie()).isNull();
assertThat(this.response.getCookie("SESSION")).isNull();
}
@Test
@@ -442,7 +437,7 @@ public class SessionRepositoryFilterTests {
wrappedRequest.getSession();
}
});
assertThat(getSessionCookie()).isNotNull();
assertThat(this.response.getCookie("SESSION")).isNotNull();
nextRequest();
@@ -454,7 +449,7 @@ public class SessionRepositoryFilterTests {
}
});
assertThat(getSessionCookie()).isNotNull();
assertThat(this.response.getCookie("SESSION")).isNotNull();
}
@Test
@@ -654,10 +649,10 @@ public class SessionRepositoryFilterTests {
}
});
ResponseCookie session = getSessionCookie();
Cookie session = getSessionCookie();
assertThat(session.isHttpOnly()).describedAs("Session Cookie should be HttpOnly")
.isTrue();
assertThat(session.isSecure())
assertThat(session.getSecure())
.describedAs("Session Cookie should be marked as Secure").isTrue();
}
@@ -1171,6 +1166,23 @@ public class SessionRepositoryFilterTests {
});
}
@Test // gh-1243
public void doFilterInclude() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest,
HttpServletResponse wrappedResponse)
throws IOException, ServletException {
String id = wrappedRequest.getSession().getId();
wrappedRequest.getRequestDispatcher("/").include(wrappedRequest,
wrappedResponse);
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
// --- HttpSessionIdResolver
@Test
@@ -1197,6 +1209,29 @@ public class SessionRepositoryFilterTests {
});
}
@Test // gh-1229
public void doFilterAdapterGetRequestedSessionIdForInvalidSession() throws Exception {
SessionRepository<MapSession> sessionRepository = new MapSessionRepository(
new HashMap<>());
this.filter = new SessionRepositoryFilter<>(sessionRepository);
this.filter.setHttpSessionIdResolver(this.strategy);
final String expectedId = "HttpSessionIdResolver-requested-id1";
final String otherId = "HttpSessionIdResolver-requested-id2";
given(this.strategy.resolveSessionIds(any(HttpServletRequest.class)))
.willReturn(Arrays.asList(expectedId, otherId));
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest,
HttpServletResponse wrappedResponse) {
assertThat(wrappedRequest.getRequestedSessionId()).isEqualTo(expectedId);
assertThat(wrappedRequest.isRequestedSessionIdValid()).isFalse();
}
});
}
@Test
public void doFilterAdapterOnNewSession() throws Exception {
this.filter.setHttpSessionIdResolver(this.strategy);
@@ -1391,132 +1426,16 @@ public class SessionRepositoryFilterTests {
.hasMessage("httpSessionIdResolver cannot be null");
}
@Test
public void bindingListenerBindListener() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerThenUnbind() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindSameListenerTwice() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindListenerOverwrite() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener1 = new CountingHttpSessionBindingListener();
CountingHttpSessionBindingListener bindingListener2 = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener1);
session.setAttribute(bindingListenerName, bindingListener2);
}
});
assertThat(bindingListener1.getCounter()).isEqualTo(0);
assertThat(bindingListener2.getCounter()).isEqualTo(1);
}
@Test
public void bindingListenerBindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
bindingListener.setThrowException();
session.setAttribute(bindingListenerName, bindingListener);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(0);
}
@Test
public void bindingListenerBindListenerThenUnbindThrowsException() throws Exception {
String bindingListenerName = "bindingListener";
CountingHttpSessionBindingListener bindingListener = new CountingHttpSessionBindingListener();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.setAttribute(bindingListenerName, bindingListener);
bindingListener.setThrowException();
session.removeAttribute(bindingListenerName);
}
});
assertThat(bindingListener.getCounter()).isEqualTo(1);
}
// --- helper methods
private void assertNewSession() {
ResponseCookie cookie = getSessionCookie();
Cookie cookie = getSessionCookie();
assertThat(cookie).isNotNull();
assertThat(cookie.getMaxAge().getSeconds()).isEqualTo(-1);
assertThat(cookie.getMaxAge()).isEqualTo(-1);
assertThat(cookie.getValue()).isNotEqualTo("INVALID");
assertThat(cookie.isHttpOnly()).describedAs("Cookie is expected to be HTTP Only")
.isTrue();
assertThat(cookie.isSecure())
assertThat(cookie.getSecure())
.describedAs(
"Cookie secured is expected to be " + this.request.isSecure())
.isEqualTo(this.request.isSecure());
@@ -1526,15 +1445,15 @@ public class SessionRepositoryFilterTests {
}
private void assertNoSession() {
ResponseCookie cookie = getSessionCookie();
Cookie cookie = getSessionCookie();
assertThat(cookie).isNull();
assertThat(this.request.getSession(false))
.describedAs("The original HttpServletRequest HttpSession should be null")
.isNull();
}
private ResponseCookie getSessionCookie() {
return ResponseCookieParser.parse(this.response, "SESSION");
private Cookie getSessionCookie() {
return this.response.getCookie("SESSION");
}
private void setSessionCookie(String sessionId) {
@@ -1557,9 +1476,6 @@ public class SessionRepositoryFilterTests {
for (Cookie cookie : this.response.getCookies()) {
nameToCookie.put(cookie.getName(), cookie);
}
ResponseCookieParser.parse(this.response)
.forEach((responseCookie) -> nameToCookie.put(responseCookie.getName(),
toServletCookie(responseCookie)));
Cookie[] nextRequestCookies = new ArrayList<>(nameToCookie.values())
.toArray(new Cookie[0]);
@@ -1590,19 +1506,6 @@ public class SessionRepositoryFilterTests {
return new String(Base64.getDecoder().decode(value));
}
private static Cookie toServletCookie(ResponseCookie responseCookie) {
Cookie cookie = new Cookie(responseCookie.getName(), responseCookie.getValue());
String domain = responseCookie.getDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge((int) responseCookie.getMaxAge().getSeconds());
cookie.setPath(responseCookie.getPath());
cookie.setSecure(responseCookie.isSecure());
cookie.setHttpOnly(responseCookie.isHttpOnly());
return cookie;
}
private static class SessionRepositoryFilterDefaultOrder implements Ordered {
@Override
@@ -1625,39 +1528,4 @@ public class SessionRepositoryFilterTests {
}
private static class CountingHttpSessionBindingListener
implements HttpSessionBindingListener {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicBoolean throwException = new AtomicBoolean(false);
@Override
public void valueBound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("bind exception");
}
this.counter.incrementAndGet();
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
if (this.throwException.get()) {
this.throwException.compareAndSet(true, false);
throw new RuntimeException("unbind exception");
}
this.counter.decrementAndGet();
}
int getCounter() {
return this.counter.get();
}
void setThrowException() {
this.throwException.compareAndSet(false, true);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
*/
public abstract class AbstractRedisITests {
private static final String DOCKER_IMAGE = "redis:4.0.10";
private static final String DOCKER_IMAGE = "redis:4.0.12";
protected static class BaseConfig {

View File

@@ -16,6 +16,8 @@
package org.springframework.session.data.redis;
import java.time.Instant;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -191,6 +193,28 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
assertThat(this.repository.findById(originalId).block()).isNull();
}
// gh-1111
@Test
public void changeSessionSaveOldSessionInstance() {
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
.createSession().block();
String sessionId = toSave.getId();
this.repository.save(toSave).block();
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
.findById(sessionId).block();
session.changeSessionId();
session.setLastAccessedTime(Instant.now());
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
this.repository.save(toSave).block();
assertThat(this.repository.findById(sessionId).block()).isNull();
assertThat(this.repository.findById(session.getId()).block()).isNotNull();
}
@Configuration
@EnableRedisWebSession
static class Config extends BaseConfig {

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@@ -190,9 +191,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -358,9 +360,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -581,6 +584,22 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
assertThat(this.repository.findById(originalId)).isNull();
}
// gh-1137
@Test
public void changeSessionIdWhenSessionIsDeleted() {
RedisSession toSave = this.repository.createSession();
String sessionId = toSave.getId();
this.repository.save(toSave);
this.repository.deleteById(sessionId);
toSave.changeSessionId();
this.repository.save(toSave);
assertThat(this.repository.findById(toSave.getId())).isNull();
assertThat(this.repository.findById(sessionId)).isNull();
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -95,7 +95,7 @@ public class RedisListenerContainerTaskExecutorITests extends AbstractRedisITest
synchronized (this.lock) {
this.lock.wait(TimeUnit.SECONDS.toMillis(1));
}
return (this.taskDispatched != null ? this.taskDispatched : Boolean.FALSE);
return (this.taskDispatched != null) ? this.taskDispatched : Boolean.FALSE;
}
}

View File

@@ -134,24 +134,36 @@ public class ReactiveRedisOperationsSessionRepository implements
@Override
public Mono<Void> save(RedisSession session) {
return session.saveDelta().and((s) -> {
if (session.isNew) {
session.setNew(false);
}
s.onComplete();
});
Mono<Void> result = session.saveChangeSessionId().and(session.saveDelta())
.and((s) -> {
session.isNew = false;
s.onComplete();
});
if (session.isNew) {
return result;
}
else {
String sessionKey = getSessionKey(
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result : Mono.empty());
}
}
@Override
public Mono<RedisSession> findById(String id) {
String sessionKey = getSessionKey(id);
// @formatter:off
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
.filter((map) -> !map.isEmpty()).map(new SessionMapper(id))
.filter((session) -> !session.isExpired()).map(RedisSession::new)
.filter((map) -> !map.isEmpty())
.map(new SessionMapper(id))
.filter((session) -> !session.isExpired())
.map(RedisSession::new)
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
// @formatter:on
}
@Override
@@ -276,12 +288,8 @@ public class ReactiveRedisOperationsSessionRepository implements
return this.cached.isExpired();
}
public void setNew(boolean isNew) {
this.isNew = isNew;
}
public boolean isNew() {
return this.isNew;
private boolean hasChangedSessionId() {
return !getId().equals(this.originalSessionId);
}
private void flushImmediateIfNecessary() {
@@ -296,38 +304,35 @@ public class ReactiveRedisOperationsSessionRepository implements
}
private Mono<Void> saveDelta() {
String sessionId = getId();
Mono<Void> changeSessionId = saveChangeSessionId(sessionId);
if (this.delta.isEmpty()) {
return changeSessionId.and(Mono.empty());
return Mono.empty();
}
String sessionKey = getSessionKey(sessionId);
String sessionKey = getSessionKey(getId());
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.opsForHash().putAll(sessionKey, this.delta);
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
.expire(sessionKey, getMaxInactiveInterval());
return changeSessionId.and(update).and(setTtl).and((s) -> {
return update.and(setTtl).and((s) -> {
this.delta.clear();
s.onComplete();
}).then();
}
private Mono<Void> saveChangeSessionId(String sessionId) {
if (sessionId.equals(this.originalSessionId)) {
private Mono<Void> saveChangeSessionId() {
if (!hasChangedSessionId()) {
return Mono.empty();
}
String sessionId = getId();
Publisher<Void> replaceSessionId = (s) -> {
this.originalSessionId = sessionId;
s.onComplete();
};
if (isNew()) {
if (this.isNew) {
return Mono.from(replaceSessionId);
}
else {

View File

@@ -28,6 +28,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.BoundHashOperations;
@@ -200,7 +202,7 @@ import org.springframework.util.Assert;
*
* <p>
* One problem with relying on Redis expiration exclusively is that Redis makes no
* guarantee of when the expired event will be fired if they key has not been accessed.
* guarantee of when the expired event will be fired if the key has not been accessed.
* Specifically the background task that Redis uses to clean up expired keys is a low
* priority task and may not trigger the key expiration. For additional details see
* <a href="http://redis.io/topics/notifications">Timing of expired events</a> section in
@@ -211,7 +213,7 @@ import org.springframework.util.Assert;
* To circumvent the fact that expired events are not guaranteed to happen we can ensure
* that each key is accessed when it is expected to expire. This means that if the TTL is
* expired on the key, Redis will remove the key and fire the expired event when we try to
* access they key.
* access the key.
* </p>
*
* <p>
@@ -252,6 +254,11 @@ public class RedisOperationsSessionRepository implements
static PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
/**
* The default Redis database used by Spring Session.
*/
public static final int DEFAULT_DATABASE = 0;
/**
* The default namespace for each key and channel in Redis used by Spring Session.
*/
@@ -284,11 +291,19 @@ public class RedisOperationsSessionRepository implements
*/
static final String SESSION_ATTR_PREFIX = "sessionAttr:";
private int database = RedisOperationsSessionRepository.DEFAULT_DATABASE;
/**
* The namespace for every key used by Spring Session in Redis.
*/
private String namespace = DEFAULT_NAMESPACE + ":";
private String sessionCreatedChannelPrefix;
private String sessionDeletedChannel;
private String sessionExpiredChannel;
private final RedisOperations<Object, Object> sessionRedisOperations;
private final RedisSessionExpirationPolicy expirationPolicy;
@@ -325,6 +340,7 @@ public class RedisOperationsSessionRepository implements
this.sessionRedisOperations = sessionRedisOperations;
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
this::getExpirationsKey, this::getSessionKey);
configureSessionChannels();
}
/**
@@ -375,6 +391,27 @@ public class RedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}.
* @param database the database index to use
*/
public void setDatabase(int database) {
this.database = database;
configureSessionChannels();
}
private void configureSessionChannels() {
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database
+ ":created:";
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
}
/**
* Returns the {@link RedisOperations} used for sessions.
* @return the {@link RedisOperations} used for sessions
* @since 2.0.0
*/
public RedisOperations<Object, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@@ -495,7 +532,7 @@ public class RedisOperationsSessionRepository implements
String channel = new String(messageChannel);
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
// TODO: is this thread safe?
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
@@ -508,8 +545,8 @@ public class RedisOperationsSessionRepository implements
return;
}
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -572,6 +609,7 @@ public class RedisOperationsSessionRepository implements
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace cannot be null or empty");
this.namespace = namespace.trim() + ":";
configureSessionChannels();
}
/**
@@ -603,17 +641,33 @@ public class RedisOperationsSessionRepository implements
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:" + "expires:";
return this.namespace + "sessions:expires:";
}
/**
* Gets the prefix for the channel that SessionCreatedEvent are published to. The
* suffix is the session id of the session that was created.
*
* @return the prefix for the channel that SessionCreatedEvent are published to
* Gets the prefix for the channel that {@link SessionCreatedEvent}s are published to.
* The suffix is the session id of the session that was created.
* @return the prefix for the channel that {@link SessionCreatedEvent}s are published
* to
*/
public String getSessionCreatedChannelPrefix() {
return this.namespace + "event:created:";
return this.sessionCreatedChannelPrefix;
}
/**
* Gets the name of the channel that {@link SessionDeletedEvent}s are published to.
* @return the name for the channel that {@link SessionDeletedEvent}s are published to
*/
public String getSessionDeletedChannel() {
return this.sessionDeletedChannel;
}
/**
* Gets the name of the channel that {@link SessionExpiredEvent}s are published to.
* @return the name for the channel that {@link SessionExpiredEvent}s are published to
*/
public String getSessionExpiredChannel() {
return this.sessionExpiredChannel;
}
/**
@@ -797,9 +851,10 @@ public class RedisOperationsSessionRepository implements
this.delta = new HashMap<>(this.delta.size());
Long originalExpiration = (this.originalLastAccessTime != null
? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli()
: null);
Long originalExpiration = (this.originalLastAccessTime != null)
? this.originalLastAccessTime.plus(getMaxInactiveInterval())
.toEpochMilli()
: null;
RedisOperationsSessionRepository.this.expirationPolicy
.onExpirationUpdated(originalExpiration, this);
}
@@ -813,8 +868,16 @@ public class RedisOperationsSessionRepository implements
originalSessionIdKey, sessionIdKey);
String originalExpiredKey = getExpiredKey(this.originalSessionId);
String expiredKey = getExpiredKey(sessionId);
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
originalExpiredKey, expiredKey);
try {
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
originalExpiredKey, expiredKey);
}
catch (NonTransientDataAccessException ex) {
if (!"ERR no such key".equals(NestedExceptionUtils
.getMostSpecificCause(ex).getMessage())) {
throw ex;
}
}
}
this.originalSessionId = sessionId;
}

View File

@@ -37,7 +37,10 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -54,6 +57,7 @@ import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -115,6 +119,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
@@ -128,9 +134,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(sessionRepository(),
Arrays.asList(new PatternTopic("__keyevent@*:del"),
new PatternTopic("__keyevent@*:expired")));
container.addMessageListener(sessionRepository(), Arrays.asList(
new ChannelTopic(sessionRepository().getSessionDeletedChannel()),
new ChannelTopic(sessionRepository().getSessionExpiredChannel())));
container.addMessageListener(sessionRepository(),
Collections.singletonList(new PatternTopic(
sessionRepository().getSessionCreatedChannelPrefix() + "*")));
@@ -256,6 +262,18 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
return redisTemplate;
}
private int resolveDatabase() {
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
}
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
}
return RedisOperationsSessionRepository.DEFAULT_DATABASE;
}
/**
* Ensures that Redis is configured to send keyspace notifications. This is important
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.

View File

@@ -183,6 +183,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveSessionNothingChanged() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
.willReturn(Mono.just(true));
@@ -191,12 +192,14 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
StepVerifier.create(this.repository.save(session)).verifyComplete();
verify(this.redisOperations).hasKey(anyString());
verifyZeroInteractions(this.redisOperations);
verifyZeroInteractions(this.hashOperations);
}
@Test
public void saveLastAccessChanged() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -206,6 +209,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
@@ -219,6 +223,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveSetAttribute() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -229,6 +234,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.setAttribute(attrName, "attrValue");
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());
@@ -242,6 +248,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
@Test
public void saveRemoveAttribute() {
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
given(this.redisOperations.expire(anyString(), any()))
@@ -252,6 +259,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
session.removeAttribute(attrName);
Mono.just(session).subscribe(this.repository::save);
verify(this.redisOperations).hasKey(anyString());
verify(this.redisOperations).opsForHash();
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
verify(this.redisOperations).expire(anyString(), any());

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -522,14 +523,15 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageCreated() throws Exception {
public void onMessageCreated() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
String channel = "spring:session:event:created:" + session.getId();
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
String channel = "spring:session:event:0:created:" + session.getId();
JdkSerializationRedisSerializer defaultSerailizer = new JdkSerializationRedisSerializer();
this.redisRepository.setDefaultSerializer(defaultSerailizer);
byte[] body = defaultSerailizer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
@@ -539,16 +541,16 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(this.event.getValue().getSessionId()).isEqualTo(session.getId());
}
// gh-309
@Test
public void onMessageCreatedCustomSerializer() throws Exception {
@Test // gh-309
public void onMessageCreatedCustomSerializer() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
byte[] body = new byte[0];
String channel = "spring:session:event:created:" + session.getId();
String channel = "spring:session:event:0:created:" + session.getId();
given(this.defaultSerializer.deserialize(body))
.willReturn(new HashMap<String, Object>());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, pattern);
@@ -559,7 +561,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionFound() throws Exception {
public void onMessageDeletedSessionFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -570,10 +572,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -586,7 +590,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionNotFound() throws Exception {
public void onMessageDeletedSessionNotFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -594,10 +598,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -608,7 +614,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionFound() throws Exception {
public void onMessageExpiredSessionFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -619,10 +625,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -635,7 +643,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionNotFound() throws Exception {
public void onMessageExpiredSessionNotFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -643,10 +651,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -881,6 +891,62 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public void onMessageCreatedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "spring:session:event:created:1:" + session.getId();
byte[] body = serializer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageDeletedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:del";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageExpiredInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:expired";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
private String getKey(String id) {
return "spring:session:sessions:" + id;
}

View File

@@ -224,7 +224,7 @@ public abstract class AbstractHazelcastRepositoryITests {
assertThat(this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
.isNotNull();
.hasSize(1);
}
}

View File

@@ -48,7 +48,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests {
private static GenericContainer container = new GenericContainer<>(
"hazelcast/hazelcast:3.10.3")
"hazelcast/hazelcast:3.9.4")
.withExposedPorts(5701)
.withEnv("JAVA_OPTS",
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")

View File

@@ -1,7 +1,7 @@
<?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 hazelcast-config-3.9.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<user-code-deployment enabled="true">
<class-cache-mode>ETERNAL</class-cache-mode>

View File

@@ -1,7 +1,7 @@
<?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 hazelcast-config-3.9.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<group>
<name>spring-session-it-test-idle-time-map-name</name>

View File

@@ -1,7 +1,7 @@
<?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 hazelcast-config-3.9.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
<group>
<name>spring-session-it-test-map-name</name>

View File

@@ -29,10 +29,11 @@ import org.springframework.session.MapSession;
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
*
* @author Vedran Pavic
* @since 2.0.5
* @since 1.3.4
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
*/
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession> {
public class SessionUpdateEntryProcessor
extends AbstractEntryProcessor<String, MapSession> {
private Instant lastAccessedTime;

View File

@@ -743,6 +743,46 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(session.<String>getAttribute("testName")).isEqualTo("testValue2");
}
@Test // gh-1151
public void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1151
public void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
session.setLastAccessedTime(Instant.now());
session.setAttribute("testName", "testValue1");
this.repository.save(session);
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1203
public void saveWithLargeAttribute() {
String attributeName = "largeAttribute";
int arraySize = 4000;
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute(attributeName, new byte[arraySize]);
this.repository.save(session);
session = this.repository.findById(session.getId());
assertThat(session).isNotNull();
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* 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.
@@ -86,7 +86,7 @@ public class MariaDb10JdbcOperationsSessionRepositoryITests
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.8");
super("mariadb:10.3.12");
}
@Override

View File

@@ -86,7 +86,7 @@ public class MariaDb5JdbcOperationsSessionRepositoryITests
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.60");
super("mariadb:5.5.62");
}
@Override

View File

@@ -85,7 +85,7 @@ public class MySql5JdbcOperationsSessionRepositoryITests
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.22");
super("mysql:5.7.24");
}
@Override

View File

@@ -85,7 +85,7 @@ public class MySql8JdbcOperationsSessionRepositoryITests
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.11");
super("mysql:8.0.13");
}
@Override

View File

@@ -86,7 +86,7 @@ public class PostgreSql10JdbcOperationsSessionRepositoryITests
extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.4");
super("postgres:10.6");
}
}

View File

@@ -86,7 +86,7 @@ public class PostgreSql9JdbcOperationsSessionRepositoryITests
extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.9");
super("postgres:9.6.11");
}
}

View File

@@ -86,9 +86,7 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
extends MSSQLServerContainer<SqlServer2007Container> {
SqlServer2007Container() {
super("microsoft/mssql-server-linux:2017-CU8");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
super("mcr.microsoft.com/mssql/server:2017-CU12");
}
}

View File

@@ -1 +1 @@
microsoft/mssql-server-linux:2017-CU8
mcr.microsoft.com/mssql/server:2017-CU12

View File

@@ -144,7 +144,9 @@ public class JdbcOperationsSessionRepository implements
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
"VALUES (?, ?, ?)";
"SELECT PRIMARY_ID, ?, ? " +
"FROM %TABLE_NAME% " +
"WHERE SESSION_ID = ?";
private static final String GET_SESSION_QUERY =
"SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
@@ -381,9 +383,9 @@ public class JdbcOperationsSessionRepository implements
ps.setLong(6, session.getExpiryTime().toEpochMilli());
ps.setString(7, session.getPrincipalName());
});
if (!session.getAttributeNames().isEmpty()) {
final List<String> attributeNames = new ArrayList<>(session.getAttributeNames());
insertSessionAttributes(session, attributeNames);
Set<String> attributeNames = session.getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(session, new ArrayList<>(attributeNames));
}
}
@@ -410,17 +412,23 @@ public class JdbcOperationsSessionRepository implements
.filter((entry) -> entry.getValue() == DeltaValue.ADDED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
insertSessionAttributes(session, addedAttributeNames);
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(session, addedAttributeNames);
}
List<String> updatedAttributeNames = session.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
updateSessionAttributes(session, updatedAttributeNames);
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(session, updatedAttributeNames);
}
List<String> removedAttributeNames = session.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
deleteSessionAttributes(session, removedAttributeNames);
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(session, removedAttributeNames);
}
}
});
@@ -490,18 +498,16 @@ public class JdbcOperationsSessionRepository implements
}
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName));
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
}
@Override
@@ -514,17 +520,15 @@ public class JdbcOperationsSessionRepository implements
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
serialize(ps, 3, session.getAttribute(attributeName));
ps.setString(1, attributeName);
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
});
}
}
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
@@ -554,9 +558,7 @@ public class JdbcOperationsSessionRepository implements
}
private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
if (attributeNames == null || attributeNames.isEmpty()) {
return;
}
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {
@@ -739,23 +741,23 @@ public class JdbcOperationsSessionRepository implements
}
if (attributeExists) {
if (attributeRemoved) {
this.delta.merge(attributeName, DeltaValue.REMOVED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
? null
: deltaValue));
this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null
: deltaValue);
}
else {
this.delta.merge(attributeName, DeltaValue.UPDATED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
? oldDeltaValue
: deltaValue));
(oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: deltaValue);
}
}
else {
this.delta.merge(attributeName, DeltaValue.ADDED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: DeltaValue.UPDATED));
: DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, attributeValue);
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -35,6 +35,9 @@ import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@@ -102,6 +105,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
if (this.lobHandler != null) {
sessionRepository.setLobHandler(this.lobHandler);
}
else if (requiresTemporaryLob(this.dataSource)) {
DefaultLobHandler lobHandler = new DefaultLobHandler();
lobHandler.setCreateTemporaryLob(true);
sessionRepository.setLobHandler(lobHandler);
}
if (this.springSessionConversionService != null) {
sessionRepository.setConversionService(this.springSessionConversionService);
}
@@ -115,6 +123,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return sessionRepository;
}
private static boolean requiresTemporaryLob(DataSource dataSource) {
try {
String productName = JdbcUtils.extractDatabaseMetaData(dataSource,
"getDatabaseProductName");
return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName));
}
catch (MetaDataAccessException ex) {
return false;
}
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}