Compare commits

..

28 Commits

Author SHA1 Message Date
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
67 changed files with 249 additions and 1048 deletions

47
Jenkinsfile vendored
View File

@@ -11,8 +11,8 @@ currentBuild.result = SUCCESS
try {
parallel check: {
stage('Check') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
timeout(time: 30, unit: 'MINUTES') {
node {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'
@@ -28,38 +28,19 @@ try {
}
}
},
jdk10: {
stage('JDK 10') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk10'
throw e
}
springio: {
stage('Spring IO') {
node {
checkout scm
try {
sh "./gradlew clean springIoCheck --stacktrace --no-daemon --refresh-dependencies -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**'"
}
}
}
},
jdk11: {
stage('JDK 11') {
timeout(time: 45, unit: 'MINUTES') {
node('ubuntu1804') {
checkout scm
try {
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies'
}
}
catch (e) {
currentBuild.result = 'FAILED: jdk11'
throw e
}
catch(e) {
currentBuild.result = 'FAILED: springio'
throw e
}
finally {
junit '**/build/spring-io*-results/*.xml'
}
}
}

View File

@@ -1,38 +1,20 @@
buildscript {
ext {
releaseBuild = version.endsWith('RELEASE')
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.1.0.M3'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
}
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.19.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
maven { url 'https://repo.spring.io/plugins-release' }
}
}
apply plugin: 'io.spring.convention.root'
group = 'org.springframework.session'
description = 'Spring Session'
gradle.taskGraph.whenReady { graph ->
def jacocoEnabled = graph.allTasks.any { it instanceof JacocoReport }
subprojects {
plugins.withType(JavaPlugin) {
sourceCompatibility = 1.8
}
plugins.withType(JacocoPlugin) {
tasks.withType(Test) {
jacoco.enabled = jacocoEnabled
}
}
}
}
ext.releaseBuild = version.endsWith('RELEASE')
ext.snapshotBuild = version.endsWith('SNAPSHOT')
ext.milestoneBuild = !(releaseBuild || snapshotBuild)

View File

@@ -614,10 +614,9 @@ Spring Session's most basic API for using a `Session` is the `SessionRepository`
This API is intentionally very simple, so that it is easy to provide additional implementations with basic functionality.
Some `SessionRepository` implementations may choose to implement `FindByIndexNameSessionRepository` also.
For example, Spring's Redis, JDBC and Hazelcast support all implement `FindByIndexNameSessionRepository`.
For example, Spring's Redis support implements `FindByIndexNameSessionRepository`.
The `FindByIndexNameSessionRepository` provides a method to look up all the sessions with a given index name and index value.
As a common use case that is supported by all provided `FindByIndexNameSessionRepository` implementations, there's a convenient method to look up all the sessions for a particular user.
The `FindByIndexNameSessionRepository` adds a single method to look up all the sessions for a particular user.
This is done by ensuring that the session attribute with the name `FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` is populated with the username.
It is the responsibility of the developer to ensure the attribute is populated since Spring Session is not aware of the authentication mechanism being used.
An example of how this might be used can be seen below:

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.
@@ -52,7 +52,9 @@ public class FindByIndexNameSessionRepositoryTests {
// tag::findby-username[]
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByPrincipalName(username);
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username);
// end::findby-username[]
}
}

View File

@@ -24,6 +24,7 @@ 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;
@@ -53,7 +54,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
// tag::rememberme-bean[]
@Bean
public SpringSessionRememberMeServices rememberMeServices() {
RememberMeServices 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.
@@ -43,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)
@@ -87,6 +86,5 @@ public class RememberMeSecurityConfigurationTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// 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.
@@ -43,7 +43,6 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@@ -87,6 +86,5 @@ public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
.isEqualTo(Duration.ofDays(30));
}
}
// end::class[]

View File

@@ -1 +1,2 @@
version=2.1.0.RC1
springBootVersion=2.0.4.RELEASE
version=2.0.6.RELEASE

View File

@@ -1,15 +1,15 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Californium-RELEASE'
mavenBom 'org.springframework:spring-framework-bom:5.1.0.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Lovelace-RELEASE'
mavenBom 'org.springframework.security:spring-security-bom:5.1.0.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.9.0-rc2'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR11'
mavenBom 'org.springframework:spring-framework-bom:5.0.9.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR10'
mavenBom 'org.springframework.security:spring-security-bom:5.0.8.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.3'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.10.5') {
dependencySet(group: 'com.hazelcast', version: '3.9.4') {
entry 'hazelcast'
entry 'hazelcast-client'
}
@@ -17,9 +17,9 @@ dependencyManagement {
dependency 'com.h2database:h2:1.4.197'
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.RELEASE'
dependency 'io.lettuce:lettuce-core:5.0.5.RELEASE'
dependency 'javax.annotation:javax.annotation-api:1.3.2'
dependency 'javax.servlet:javax.servlet-api:4.0.1'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:8.0.12'
dependency 'org.apache.derby:derby:10.14.2.0'

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.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

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.
@@ -44,7 +44,10 @@ public class IndexController {
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions
.findByPrincipalName(principal.getName()).values();
.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName())
.values();
model.addAttribute("sessions", usersSessions);
return "index";
}
@@ -53,8 +56,9 @@ public class IndexController {
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
public String removeSession(Principal principal,
@PathVariable String sessionIdToDelete) {
Set<String> usersSessionIds = this.sessions
.findByPrincipalName(principal.getName()).keySet();
Set<String> usersSessionIds = this.sessions.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principal.getName()).keySet();
if (usersSessionIds.contains(sessionIdToDelete)) {
this.sessions.deleteById(sessionIdToDelete);
}

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,6 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
/**
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc

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 +0,0 @@
ryuk.container.timeout=120

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 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -23,6 +23,5 @@ dependencies {
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.springframework.security:spring-security-test"
integrationTestCompile "org.testcontainers:testcontainers"
testCompile "org.testcontainers:testcontainers"
}

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -5,7 +5,7 @@ dependencyManagement {
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.32.0'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.29.3'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
dependency 'org.webjars:bootstrap:2.3.2'

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

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 +0,0 @@
ryuk.container.timeout=120

View File

@@ -17,6 +17,8 @@ dependencies {
testCompile "org.springframework.security:spring-security-test"
testCompile "org.assertj:assertj-core"
testCompile "org.springframework:spring-test"
integrationTestCompile "org.testcontainers:testcontainers"
}
gretty {

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

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 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -1,16 +1,13 @@
rootProject.name = 'spring-session'
rootProject.name = 'spring-session-build'
FileTree buildFiles = fileTree(rootDir) {
include '**/*.gradle'
exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*'
exclude '**/grails3'
gradle.startParameter.projectProperties.get('excludeProjects')?.split(',')?.each { excludeProject ->
exclude excludeProject
}
}
String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { buildFile ->
buildFiles.each { File buildFile ->
if (buildFile.name == 'build.gradle') {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')

View File

@@ -6,7 +6,6 @@ dependencies {
compile "org.springframework:spring-jcl"
optional "io.projectreactor:reactor-core"
optional "javax.annotation:javax.annotation-api"
optional "javax.servlet:javax.servlet-api"
optional "org.springframework:spring-context"
optional "org.springframework:spring-jdbc"

View File

@@ -19,22 +19,27 @@ package org.springframework.session;
import java.util.Map;
/**
* Extends a basic {@link SessionRepository} to allow finding sessions by the specified
* index name and index value.
* Extends a basic {@link SessionRepository} to allow finding a session id by the
* principal name. The principal name is defined by the {@link Session} attribute with the
* name {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME}.
*
* @param <S> the type of Session being managed by this
* {@link FindByIndexNameSessionRepository}
* @author Rob Winch
* @author Vedran Pavic
*/
public interface FindByIndexNameSessionRepository<S extends Session>
extends SessionRepository<S> {
/**
* A session index that contains the current principal name (i.e. username).
* <p>
* It is the responsibility of the developer to ensure the index is populated since
* Spring Session is not aware of the authentication mechanism being used.
* A common session attribute that contains the current principal name (i.e.
* username).
* </p>
*
* <p>
* It is the responsibility of the developer to ensure the attribute is populated
* since Spring Session is not aware of the authentication mechanism being used.
* </p>
*
* @since 1.1
*/
@@ -42,34 +47,17 @@ public interface FindByIndexNameSessionRepository<S extends Session>
.concat(".PRINCIPAL_NAME_INDEX_NAME");
/**
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the specified index name index value.
* Find a Map of the session id to the {@link Session} of all sessions that contain
* the session attribute with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
* the specified principal name.
*
* @param indexName the name of the index (i.e.
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME})
* @param indexValue the value of the index to search for.
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified index name and index value. If no
* results are found, an empty {@code Map} is returned.
* @return a Map (never null) of the session id to the {@link Session} of all sessions
* that contain the session specified index name and the value of the specified index
* name. If no results are found, an empty Map is returned.
*/
Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);
/**
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
* contain the index with the name
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the
* specified principal name.
*
* @param principalName the principal name
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
* of all sessions that contain the specified principal name. If no results are found,
* an empty {@code Map} is returned.
* @since 2.1.0
*/
default Map<String, S> findByPrincipalName(String principalName) {
return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
}
}

View File

@@ -53,7 +53,7 @@ public final class MapSession implements Session, Serializable {
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
private String id;
private final String originalId;
private String originalId;
private Map<String, Object> sessionAttrs = new HashMap<>();
private Instant creationTime = Instant.now();
private Instant lastAccessedTime = this.creationTime;
@@ -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

@@ -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.
@@ -65,8 +65,9 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository
.findByPrincipalName(name(principal)).values();
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session

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

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

@@ -87,6 +87,12 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
return Mono.just(session);
}
public Mono<Void> storeSession(WebSession session) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springWebSession = (SpringSessionWebSession) session;
return this.sessions.save(springWebSession.session);
}
@Override
public Mono<WebSession> retrieveSession(String sessionId) {
return this.sessions.findById(sessionId)

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.
@@ -17,7 +17,6 @@
package org.springframework.session.security;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -75,9 +74,7 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isFalse();
}
@@ -93,9 +90,7 @@ public class SpringSessionBackedSessionRegistryTest {
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isTrue();
}
@@ -167,8 +162,9 @@ public class SpringSessionBackedSessionRegistryTest {
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByPrincipalName(USER_NAME))
.thenReturn(sessions);
when(this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, USER_NAME))
.thenReturn(sessions);
}
}

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.

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.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
@@ -467,39 +466,6 @@ public class DefaultCookieSerializerTests {
assertThat(getCookie().getMaxAge()).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();
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
this.serializer.setCookieName(cookieName);
@@ -512,8 +478,8 @@ public class DefaultCookieSerializerTests {
return new Cookie(name, value);
}
private MockCookie getCookie() {
return (MockCookie) this.response.getCookie(this.cookieName);
private Cookie getCookie() {
return this.response.getCookie(this.cookieName);
}
private String getCookieValue() {

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;
@@ -1390,122 +1386,6 @@ 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() {
@@ -1608,39 +1488,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

@@ -253,6 +253,17 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
}
@Test
public void storeSessionWhenInvokedThenSessionSaved() {
given(this.sessionRepository.save(this.createSession)).willReturn(Mono.empty());
WebSession createdSession = this.webSessionStore.createWebSession()
.block();
this.webSessionStore.storeSession(createdSession).block();
verify(this.sessionRepository).save(this.createSession);
}
@Test
public void retrieveSessionThenStarted() {
String id = "id";

View File

@@ -30,7 +30,6 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
@@ -210,10 +209,7 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
this.repository.save(session).block();
toSave.setLastAccessedTime(Instant.now());
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> this.repository.save(toSave).block())
.withMessage("Session was invalidated");
this.repository.save(toSave).block();
assertThat(this.repository.findById(sessionId).block()).isNull();
assertThat(this.repository.findById(session.getId()).block()).isNotNull();

View File

@@ -16,7 +16,6 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@@ -191,10 +190,9 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -360,10 +358,9 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -118,15 +118,6 @@ public class ReactiveRedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Returns the {@link ReactiveRedisOperations} used for sessions.
* @return the {@link ReactiveRedisOperations} used for sessions
* @since 2.1.0
*/
public ReactiveRedisOperations<String, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@Override
public Mono<RedisSession> createSession() {
return Mono.defer(() -> {
@@ -156,9 +147,7 @@ public class ReactiveRedisOperationsSessionRepository implements
session.hasChangedSessionId() ? session.originalSessionId
: session.getId());
return this.sessionRedisOperations.hasKey(sessionKey)
.flatMap((exists) -> exists ? result
: Mono.error(new IllegalStateException(
"Session was invalidated")));
.flatMap((exists) -> exists ? result : Mono.empty());
}
}

View File

@@ -254,11 +254,6 @@ 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.
*/
@@ -291,19 +286,11 @@ 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;
@@ -340,7 +327,6 @@ public class RedisOperationsSessionRepository implements
this.sessionRedisOperations = sessionRedisOperations;
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
this::getExpirationsKey, this::getSessionKey);
configureSessionChannels();
}
/**
@@ -391,27 +377,6 @@ 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;
}
@@ -532,7 +497,7 @@ public class RedisOperationsSessionRepository implements
String channel = new String(messageChannel);
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
// TODO: is this thread safe?
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
@@ -545,8 +510,8 @@ public class RedisOperationsSessionRepository implements
return;
}
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -609,7 +574,6 @@ public class RedisOperationsSessionRepository implements
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace cannot be null or empty");
this.namespace = namespace.trim() + ":";
configureSessionChannels();
}
/**
@@ -641,33 +605,17 @@ public class RedisOperationsSessionRepository implements
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:expires:";
return this.namespace + "sessions:" + "expires:";
}
/**
* 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
* 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
*/
public String getSessionCreatedChannelPrefix() {
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;
return this.namespace + "event:created:";
}
/**

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.
@@ -23,14 +23,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
/**
* Annotation used to inject the Redis accessor used by Spring Session's Redis session
* repository.
* Annotation used to inject the {@link RedisOperations} instance used by Spring Session's
* {@link RedisOperationsSessionRepository}.
*
* @author Vedran Pavic
* @see org.springframework.session.data.redis.RedisOperationsSessionRepository#getSessionRedisOperations()
* @see org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository#getSessionRedisOperations()
* @since 2.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,

View File

@@ -37,10 +37,7 @@ 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;
@@ -57,7 +54,6 @@ 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;
@@ -119,8 +115,6 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
@@ -134,9 +128,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(sessionRepository(), Arrays.asList(
new ChannelTopic(sessionRepository().getSessionDeletedChannel()),
new ChannelTopic(sessionRepository().getSessionExpiredChannel())));
container.addMessageListener(sessionRepository(),
Arrays.asList(new PatternTopic("__keyevent@*:del"),
new PatternTopic("__keyevent@*:expired")));
container.addMessageListener(sessionRepository(),
Collections.singletonList(new PatternTopic(
sessionRepository().getSessionCreatedChannelPrefix() + "*")));
@@ -262,18 +256,6 @@ 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

@@ -21,7 +21,6 @@ import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -65,8 +64,6 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisConnectionFactory redisConnectionFactory;
private RedisSerializer<Object> defaultRedisSerializer;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@@ -110,13 +107,6 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
this.redisConnectionFactory = redisConnectionFactoryToUse;
}
@Autowired(required = false)
@Qualifier("springSessionDefaultRedisSerializer")
public void setDefaultRedisSerializer(
RedisSerializer<Object> defaultRedisSerializer) {
this.defaultRedisSerializer = defaultRedisSerializer;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -144,11 +134,10 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
private ReactiveRedisTemplate<String, Object> createReactiveRedisTemplate() {
RedisSerializer<String> keySerializer = new StringRedisSerializer();
RedisSerializer<Object> defaultSerializer = (this.defaultRedisSerializer != null)
? this.defaultRedisSerializer
: new JdkSerializationRedisSerializer(this.classLoader);
RedisSerializer<Object> valueSerializer = new JdkSerializationRedisSerializer(
this.classLoader);
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
.<String, Object>newSerializationContext(defaultSerializer)
.<String, Object>newSerializationContext(valueSerializer)
.key(keySerializer).hashKey(keySerializer).build();
return new ReactiveRedisTemplate<>(this.redisConnectionFactory,
serializationContext);

View File

@@ -346,16 +346,12 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime()
.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getMaxInactiveInterval())
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(
session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime()
.truncatedTo(ChronoUnit.MILLIS));
}).verifyComplete();
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
}).verifyComplete();
}
@Test

View File

@@ -16,7 +16,6 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -432,12 +431,12 @@ public class RedisOperationsSessionRepositoryTests {
.isEqualTo(expected.getAttribute(attribute1));
assertThat(session.<String>getAttribute(attribute2))
.isEqualTo(expected.getAttribute(attribute2));
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getCreationTime().truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getCreationTime()).isEqualTo(expected.getCreationTime());
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expected.getMaxInactiveInterval());
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(expected.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getLastAccessedTime())
.isEqualTo(expected.getLastAccessedTime());
}
@Test
@@ -498,11 +497,9 @@ public class RedisOperationsSessionRepositoryTests {
RedisSession session = sessionIdToSessions.get(sessionId);
assertThat(session).isNotNull();
assertThat(session.getId()).isEqualTo(sessionId);
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(lastAccessed.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessed);
assertThat(session.getMaxInactiveInterval()).isEqualTo(maxInactive);
assertThat(session.getCreationTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(createdTime.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getCreationTime()).isEqualTo(createdTime);
}
@Test
@@ -525,15 +522,14 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageCreated() {
public void onMessageCreated() throws Exception {
MapSession session = this.cached;
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
String channel = "spring:session:event:0:created:" + session.getId();
byte[] pattern = "".getBytes("UTF-8");
String channel = "spring:session:event:created:" + session.getId();
JdkSerializationRedisSerializer defaultSerailizer = new JdkSerializationRedisSerializer();
this.redisRepository.setDefaultSerializer(defaultSerailizer);
byte[] body = defaultSerailizer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
@@ -543,16 +539,16 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(this.event.getValue().getSessionId()).isEqualTo(session.getId());
}
@Test // gh-309
public void onMessageCreatedCustomSerializer() {
// gh-309
@Test
public void onMessageCreatedCustomSerializer() throws Exception {
MapSession session = this.cached;
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
byte[] pattern = "".getBytes("UTF-8");
byte[] body = new byte[0];
String channel = "spring:session:event:0:created:" + session.getId();
String channel = "spring:session:event:created:" + session.getId();
given(this.defaultSerializer.deserialize(body))
.willReturn(new HashMap<String, Object>());
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, pattern);
@@ -563,7 +559,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionFound() {
public void onMessageDeletedSessionFound() throws Exception {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -574,12 +570,10 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -592,7 +586,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionNotFound() {
public void onMessageDeletedSessionNotFound() throws Exception {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -600,12 +594,10 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -616,7 +608,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionFound() {
public void onMessageExpiredSessionFound() throws Exception {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -627,12 +619,10 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -645,7 +635,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionNotFound() {
public void onMessageExpiredSessionNotFound() throws Exception {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -653,12 +643,10 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -893,62 +881,6 @@ 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

@@ -27,12 +27,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -73,22 +70,6 @@ public class RedisWebSessionConfigurationTests {
assertThat(repository).isNotNull();
}
@Test
public void springSessionRedisOperationsResolvingConfiguration() {
registerAndRefresh(RedisConfig.class,
SpringSessionRedisOperationsResolvingConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
assertThat(repository).isNotNull();
ReactiveRedisOperations<String, Object> springSessionRedisOperations = this.context
.getBean(SpringSessionRedisOperationsResolvingConfig.class)
.getSpringSessionRedisOperations();
assertThat(springSessionRedisOperations).isNotNull();
assertThat((ReactiveRedisOperations) ReflectionTestUtils.getField(repository,
"sessionRedisOperations")).isEqualTo(springSessionRedisOperations);
}
@Test
public void customNamespace() {
registerAndRefresh(RedisConfig.class, CustomNamespaceConfig.class);
@@ -200,36 +181,6 @@ public class RedisWebSessionConfigurationTests {
.hasMessageContaining("expected single matching bean but found 2");
}
@Test
@SuppressWarnings("unchecked")
public void customRedisSerializerConfig() {
registerAndRefresh(RedisConfig.class, CustomRedisSerializerConfig.class);
ReactiveRedisOperationsSessionRepository repository = this.context
.getBean(ReactiveRedisOperationsSessionRepository.class);
RedisSerializer<Object> redisSerializer = this.context
.getBean("springSessionDefaultRedisSerializer", RedisSerializer.class);
assertThat(repository).isNotNull();
assertThat(redisSerializer).isNotNull();
ReactiveRedisOperations redisOperations = (ReactiveRedisOperations) ReflectionTestUtils
.getField(repository, "sessionRedisOperations");
assertThat(redisOperations).isNotNull();
RedisSerializationContext serializationContext = redisOperations
.getSerializationContext();
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getReader(),
"serializer")).isEqualTo(redisSerializer);
assertThat(ReflectionTestUtils.getField(
serializationContext.getHashValueSerializationPair().getWriter(),
"serializer")).isEqualTo(redisSerializer);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -250,18 +201,6 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class SpringSessionRedisOperationsResolvingConfig {
@SpringSessionRedisOperations
private ReactiveRedisOperations<String, Object> springSessionRedisOperations;
public ReactiveRedisOperations<String, Object> getSpringSessionRedisOperations() {
return this.springSessionRedisOperations;
}
}
@EnableRedisWebSession(redisNamespace = REDIS_NAMESPACE)
static class CustomNamespaceConfig {
@@ -336,15 +275,4 @@ public class RedisWebSessionConfigurationTests {
}
@EnableRedisWebSession
static class CustomRedisSerializerConfig {
@Bean
@SuppressWarnings("unchecked")
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return mock(RedisSerializer.class);
}
}
}

View File

@@ -3,7 +3,6 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
compile project(':spring-session-core')
compile "com.hazelcast:hazelcast"
compile "javax.annotation:javax.annotation-api"
compile "org.springframework:spring-context"
testCompile "javax.servlet:javax.servlet-api"

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.5")
"hazelcast/hazelcast:3.9.4")
.withExposedPorts(5701)
.withEnv("JAVA_OPTS",
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -20,7 +20,6 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import com.hazelcast.core.Offloadable;
import com.hazelcast.map.AbstractEntryProcessor;
import com.hazelcast.map.EntryProcessor;
@@ -33,8 +32,7 @@ import org.springframework.session.MapSession;
* @since 2.0.5
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
*/
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession>
implements Offloadable {
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession> {
private Instant lastAccessedTime;
@@ -68,11 +66,6 @@ class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSess
return Boolean.TRUE;
}
@Override
public String getExecutorName() {
return OFFLOADABLE_EXECUTOR;
}
void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}

View File

@@ -21,7 +21,6 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import javax.sql.DataSource;
@@ -39,7 +38,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
@@ -174,8 +172,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.isExpired()).isFalse();
assertThat(session.getLastAccessedTime().truncatedTo(ChronoUnit.MILLIS))
.isEqualTo(lastAccessedTime.truncatedTo(ChronoUnit.MILLIS));
assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessedTime);
}
@Test
@@ -746,7 +743,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(session.<String>getAttribute("testName")).isEqualTo("testValue2");
}
@Test // gh-1031
@Test // gh-1151
public void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
@@ -758,7 +755,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1031
@Test // gh-1151
public void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
@@ -771,31 +768,6 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1133
public void sessionFromStoreResolvesAttributesLazily() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
this.repository.save(session);
session = this.repository.findById(session.getId());
MapSession delegate = (MapSession) ReflectionTestUtils.getField(session,
"delegate");
assertThat((String) session.getAttribute("attribute1")).isEqualTo("value1");
assertThat(delegate).isNotNull();
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute1"), "value"))
.isEqualTo("value1");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isNull();
assertThat((String) session.getAttribute("attribute2")).isEqualTo("value2");
assertThat(ReflectionTestUtils
.getField((Supplier) delegate.getAttribute("attribute2"), "value"))
.isEqualTo("value2");
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}
@@ -813,5 +785,4 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
}
}
}

View File

@@ -86,7 +86,7 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
extends MSSQLServerContainer<SqlServer2007Container> {
SqlServer2007Container() {
super("microsoft/mssql-server-linux:2017-CU10");
super("microsoft/mssql-server-linux:2017-CU9");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
}

View File

@@ -1 +1 @@
microsoft/mssql-server-linux:2017-CU10
microsoft/mssql-server-linux:2017-CU9

View File

@@ -1 +0,0 @@
ryuk.container.timeout=120

View File

@@ -28,7 +28,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@@ -53,9 +52,7 @@ import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
@@ -129,7 +126,6 @@ import org.springframework.util.StringUtils;
* target database type.
*
* @author Vedran Pavic
* @author Craig Andrews
* @since 1.2.0
*/
public class JdbcOperationsSessionRepository implements
@@ -193,17 +189,10 @@ public class JdbcOperationsSessionRepository implements
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private TransactionOperations transactionOperations = new TransactionOperations() {
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
return action.doInTransaction(null);
}
};
/**
* The name of database table used by Spring Session to store sessions.
*/
@@ -240,29 +229,14 @@ public class JdbcOperationsSessionRepository implements
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will execute all data access operations in a transaction with
* propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations);
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionOperations = createTransactionTemplate(transactionManager);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will not execute data access operations in a transaction.
* @param jdbcOperations the {@link JdbcOperations} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = createTransactionTemplate(transactionManager);
this.conversionService = createDefaultConversionService();
prepareQueries();
}
@@ -532,7 +506,7 @@ public class JdbcOperationsSessionRepository implements
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
}
@@ -547,7 +521,7 @@ public class JdbcOperationsSessionRepository implements
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
setObjectAsBlob(ps, 2, session.getAttribute(attributeName));
serialize(ps, 2, session.getAttribute(attributeName));
ps.setString(3, session.getId());
});
}
@@ -561,7 +535,7 @@ public class JdbcOperationsSessionRepository implements
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
serialize(ps, 1, session.getAttribute(attributeName));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@@ -576,7 +550,7 @@ public class JdbcOperationsSessionRepository implements
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
setObjectAsBlob(ps, 1, session.getAttribute(attributeName));
serialize(ps, 1, session.getAttribute(attributeName));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
@@ -659,17 +633,19 @@ public class JdbcOperationsSessionRepository implements
getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
}
private void setObjectAsBlob(PreparedStatement ps, int paramIndex, Object object)
private void serialize(PreparedStatement ps, int paramIndex, Object attributeValue)
throws SQLException {
byte[] bytes = (byte[]) this.conversionService.convert(object,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, bytes);
this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex,
(byte[]) this.conversionService.convert(attributeValue,
TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class)));
}
private Object getBlobAsObject(ResultSet rs, String columnName) throws SQLException {
byte[] bytes = this.lobHandler.getBlobAsBytes(rs, columnName);
return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
private Object deserialize(ResultSet rs, String columnName)
throws SQLException {
return this.conversionService.convert(
this.lobHandler.getBlobAsBytes(rs, columnName),
TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
@@ -679,28 +655,6 @@ public class JdbcOperationsSessionRepository implements
}
private static <T> Supplier<T> value(T value) {
return (value != null) ? () -> value : null;
}
private static <T> Supplier<T> lazily(Supplier<T> supplier) {
Supplier<T> lazySupplier = new Supplier<T>() {
private T value;
@Override
public T get() {
if (this.value == null) {
this.value = supplier.get();
}
return this.value;
}
};
return (supplier != null) ? lazySupplier : null;
}
/**
* The {@link Session} to use for {@link JdbcOperationsSessionRepository}.
*
@@ -770,8 +724,7 @@ public class JdbcOperationsSessionRepository implements
@Override
public <T> T getAttribute(String attributeName) {
Supplier<T> supplier = this.delegate.getAttribute(attributeName);
return (supplier != null) ? supplier.get() : null;
return this.delegate.getAttribute(attributeName);
}
@Override
@@ -806,7 +759,7 @@ public class JdbcOperationsSessionRepository implements
? oldDeltaValue
: DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, value(attributeValue));
this.delegate.setAttribute(attributeName, attributeValue);
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||
SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
@@ -898,8 +851,7 @@ public class JdbcOperationsSessionRepository implements
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
Object attributeValue = getBlobAsObject(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName, lazily(() -> attributeValue));
session.delegate.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
}
sessions.add(session);
}

View File

@@ -42,10 +42,7 @@ import org.springframework.transaction.TransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
@@ -91,7 +88,7 @@ public class JdbcOperationsSessionRepositoryTests {
assertThatThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("TransactionManager must not be null");
.hasMessage("Property 'transactionManager' is required");
}
@Test
@@ -691,89 +688,6 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public void saveNewWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(
startsWith("INSERT INTO SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void saveUpdatedWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(
"primaryKey", new MapSession());
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
public void findByIdWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class),
any(ResultSetExtractor.class))).willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findById("testSessionId");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.SESSION_ID = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void deleteByIdWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.deleteById("testSessionId");
verify(this.jdbcOperations, times(1)).update(
eq("DELETE FROM SPRING_SESSION WHERE SESSION_ID = ?"), anyString());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
public void findByIndexNameAndIndexValueWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class),
any(ResultSetExtractor.class))).willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
"testIndexValue");
verify(this.jdbcOperations, times(1)).query(
endsWith("WHERE S.PRINCIPAL_NAME = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
public void cleanUpExpiredSessionsWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.cleanUpExpiredSessions();
verify(this.jdbcOperations, times(1)).update(
eq("DELETE FROM SPRING_SESSION WHERE EXPIRY_TIME < ?"), anyLong());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
private void assertPropagationRequiresNew() {
ArgumentCaptor<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);