Compare commits

..

53 Commits

Author SHA1 Message Date
Joe Grandja
56033a9b68 Release version 2.0.0.M3 2017-07-25 09:31:12 -04:00
Vedran Pavic
99a2b079ac Upgrade dependencies to current milestones
- Spring Framework 5.0.0.RC3
- Spring Data Kay-RC1
- Spring Security 5.0.0.M3
2017-07-25 10:11:26 +02:00
Vedran Pavic
9120151692 Polish "Add WebFlux Support"
Closes gh-683
2017-07-21 15:31:45 +02:00
Rob Winch
5abbe66b1d Add WebFlux Support 2017-07-21 15:30:19 +02:00
Rob Winch
f00c196430 Update Dependencies to Prepare Release 2017-07-20 22:24:35 -05:00
Rob Winch
be2604ca69 Add Session.changeSessionId 2017-07-20 16:31:38 -05:00
Rob Winch
2aa71ffb6d Update to lettuce 5.0.0.RC1 2017-07-20 09:54:53 -05:00
Rob Winch
8bdcba6e50 Spring Session 2.0.0.M2 w/ Boot Samples
This is necessary because the current version of Spring Session is not
compatible with Boot 2.0.0.M2 and we cannot release against the SNAPSHOTs

Issue gh-833
2017-07-18 20:09:21 -05:00
Mark Paluch
8dd1a10f1b Adapt to changes in Spring Data Redis API 2017-07-18 16:19:05 +02:00
Vedran Pavic
1d247aa96f Align Checkstyle config with spring-build-conventions 2017-07-12 08:24:09 +02:00
Vedran Pavic
c00d6a7bf2 Apply correct convention plugin for modules 2017-07-10 09:10:33 +02:00
Vedran Pavic
c0df3bf28b Fix deprecation warnings 2017-07-10 07:28:09 +02:00
Vedran Pavic
1b8c9838a4 Fix unchecked operations build warning 2017-07-10 07:27:27 +02:00
Vedran Pavic
8a1b454121 Fix javadoc build warning 2017-07-10 07:26:35 +02:00
Vedran Pavic
ef69c8169a Polish dependencies
This commit removes needless dependency exclusions.

See gh-824
2017-07-10 07:20:35 +02:00
Rob Winch
40b3d07224 Revert "Add --debug to Jenkinsfile"
This reverts commit 9c4e20f074.
2017-07-07 11:23:54 -05:00
Rob Winch
8c726f2215 Use Gradle 3.5.1
This works around a bug in 4.0 where Gradle is hanging when trying to
resolve a configuration.
2017-07-07 11:17:32 -05:00
Rob Winch
c2a86a27ce Travis skip install 2017-07-07 10:16:13 -05:00
Rob Winch
6a08ef6f97 Polish travis build 2017-07-07 10:03:54 -05:00
Rob Winch
9c4e20f074 Add --debug to Jenkinsfile
Try and troubleshoot why the build is hanging
2017-07-07 09:42:24 -05:00
Vedran Pavic
5845a9c46a Improve dependency management
This commit improves dependency management with the following changes:

 - `spring-session-core`: move `javax.servlet-api` from `provided` to `optional` configuration due to introduction of reactive support
 - `spring-session-data-redis`: remove Redis driver from `compile` configuration
 - Boot samples: delegate Redis driver choice to `spring-boot-starter-data-redis`
 - polish `test` configuration dependencies
2017-07-06 17:05:01 -05:00
Rob Winch
7c6693a268 Remove Sonar to see if it fixes build 2017-07-06 08:49:21 -05:00
Vedran Pavic
05a3f59813 Simplify Hazelcast sample 2017-07-05 16:00:28 -05:00
Vedran Pavic
47a7a35aa4 Remove use of Assert#notNull from core components
Fixes gh-820
2017-07-05 17:04:03 +02:00
Vedran Pavic
04b4fe3e3b Fix Checkstyle violations 2017-07-01 00:04:46 +02:00
Rob Winch
36bb65e4b5 Add default methods to Session
Fixes gh-819
2017-06-30 10:25:14 -05:00
Rob Winch
8ef36e4f3e Session Optional<T> getAttribute -> T getAttribute
Issue gh-819
2017-06-30 10:24:59 -05:00
Rob Winch
ab3e280993 Update to latest SNAPSHOTs 2017-06-29 22:20:26 -05:00
Rob Winch
30562b5749 Use Spring IO Cairo-BUILD-SNAPSHOT 2017-06-27 16:37:13 -05:00
Rob Winch
d42a7b65ea Add MapReactorSessionRepository
Fixes gh-815
2017-06-27 16:32:51 -05:00
Rob Winch
db9807d12b Add ReactorSessionRepository
Fixes gh-814
2017-06-27 16:32:51 -05:00
Rob Winch
db09fa8168 Use SPRING_SESSION_TEAM_EMAILS 2017-06-26 10:52:17 -05:00
Vedran Pavic
031541bc05 Fix Checkstyle violations 2017-06-26 08:45:20 +02:00
Vedran Pavic
084e3428fb Move SessionEventRegistry back to integration-test sources
Fixes gh-810
2017-06-23 19:03:46 +02:00
Vedran Pavic
b321ff02f0 Revert "SessionRepository.save returns S"
See gh-809
2017-06-23 18:48:30 +02:00
Rob Winch
c6c6beb40c Session.delete -> deleteById
Fixes gh-809
2017-06-22 21:30:14 -05:00
Rob Winch
0127ef9f9b SessionRepository.getSession(String) -> findById(String)
Issue gh-809
2017-06-22 21:29:34 -05:00
Rob Winch
cd8686ae9c SessionRepository.save returns S
Issue gh-809
2017-06-22 21:27:25 -05:00
Rob Winch
233d179bfa Revert "Allow Publishing When Spring IO Fails"
This reverts commit fca411996a.
2017-06-22 13:20:23 -05:00
Rob Winch
4e8ae8d9d4 Revert "Deploy Without Checks for Spring IO"
This reverts commit 8c6810c6dd.
2017-06-22 13:20:21 -05:00
Rob Winch
8c6810c6dd Deploy Without Checks for Spring IO 2017-06-22 13:18:01 -05:00
Rob Winch
fca411996a Allow Publishing When Spring IO Fails
This is necessary to solve the problem of chicken and the Egg. See
https://github.com/spring-io/platform/issues/622#issuecomment-310452646
2017-06-22 13:00:12 -05:00
Rob Winch
79b8296e1c Work Around spring-projects/spring-boot#9573
Issue gh-806
2017-06-22 09:04:23 -05:00
Rob Winch
043cb42149 Extract spring-session-jdbc
Issue gh-806
2017-06-22 09:04:09 -05:00
Rob Winch
c28f047eb5 Extract spring-session-data-hazelcast
Issue gh-806
2017-06-22 09:03:50 -05:00
Rob Winch
972cf66d7e Extract spring-session-data-redis
Issue gh-806
2017-06-22 09:03:25 -05:00
Rob Winch
f1319483ee Move spring-session to spring-session-core
Issue gh-806
2017-06-22 09:02:44 -05:00
Rob Winch
6ad5006280 Update to Gradle 4.0 2017-06-16 13:40:43 -05:00
Vedran Pavic
f7e07b7f6b Improve Session API to use Java 8 2017-06-16 11:44:19 -05:00
Vedran Pavic
4cf26d9c36 Move ExpiringSession API into Session 2017-06-16 11:44:19 -05:00
Vedran Pavic
a848df1235 Replace explicit type arguments with diamond operator 2017-06-16 11:44:19 -05:00
Rob Winch
f8292ba512 Update to Spring Boot 2.0.0.M2
Fixes gh-801
2017-06-16 10:29:30 -05:00
Rob Winch
21bcc6e8d7 Next Development Version 2017-06-15 20:35:20 -05:00
193 changed files with 3082 additions and 1258 deletions

View File

@@ -16,4 +16,5 @@ cache:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
script: ./gradlew build
install: true
script: ./gradlew clean build --refresh-dependencies --no-daemon

17
Jenkinsfile vendored
View File

@@ -24,21 +24,6 @@ try {
}
}
},
sonar: {
stage('Sonar') {
node {
checkout scm
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
try {
sh "./gradlew clean sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon"
} catch(Exception e) {
currentBuild.result = 'FAILED: sonar'
throw e
}
}
}
}
},
springio: {
stage('Spring IO') {
node {
@@ -95,7 +80,7 @@ try {
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SECURITY_TEAM_EMAILS"
to: "$SPRING_SESSION_TEAM_EMAILS"
)
}
}

View File

@@ -1,11 +1,10 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.2.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.3.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
repositories {
maven { url 'https://repo.spring.io/libs-snapshot' }
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven { url 'https://repo.spring.io/plugins-release' }
}
}
apply plugin: 'io.spring.convention.root'

View File

@@ -2,8 +2,10 @@ apply plugin: 'io.spring.convention.docs'
apply plugin: 'io.spring.convention.spring-test'
dependencies {
testCompile project(':spring-session')
testCompile project(':spring-session-core')
testCompile project(':spring-session-data-redis')
testCompile project(':spring-session-hazelcast')
testCompile project(':spring-session-jdbc')
testCompile "org.springframework:spring-jdbc"
testCompile "org.springframework:spring-messaging"
testCompile "org.springframework:spring-webmvc"
@@ -39,5 +41,5 @@ asciidoctor {
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/',
'samples-dir' : rootProject.projectDir.path + '/samples/',
'session-main-resources-dir' : rootProject.projectDir.path + '/spring-session/src/main/resources/'
'session-jdbc-main-resources-dir' : project(':spring-session-jdbc').projectDir.path + '/src/main/resources/'
}

View File

@@ -416,7 +416,7 @@ include::{docs-test-dir}docs/security/SecurityConfiguration.java[tags=class]
----
This assumes that you've also configured Spring Session to provide a `FindByIndexNameSessionRepository` that
returns `ExpiringSession` instances.
returns `Session` instances.
When using XML configuration, it would look something like this:
[source,xml,indent=0]
@@ -458,11 +458,7 @@ include::{indexdoc-tests}[tags=repository-demo]
<5> We retrieve the `Session` from the `SessionRepository`.
<6> We obtain the persisted `User` from our `Session` without the need for explicitly casting our attribute.
[[api-expiringsession]]
=== ExpiringSession
An `ExpiringSession` extends a `Session` by providing attributes related to the `Session` instance's expiration.
If there is no need to interact with the expiration information, prefer using the more simple `Session` API.
`Session` API also provides attributes related to the `Session` instance's expiration.
Typical usage might look like the following:
@@ -471,17 +467,17 @@ Typical usage might look like the following:
include::{indexdoc-tests}[tags=expire-repository-demo]
----
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `ExpiringSession`. The generic type is defined in our class.
<2> We create a new `ExpiringSession` using our `SessionRepository` and assign it to a variable of type `S`.
<3> We interact with the `ExpiringSession`.
In our example, we demonstrate updating the amount of time the `ExpiringSession` can be inactive before it expires.
<4> We now save the `ExpiringSession`.
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `Session`. The generic type is defined in our class.
<2> We create a new `Session` using our `SessionRepository` and assign it to a variable of type `S`.
<3> We interact with the `Session`.
In our example, we demonstrate updating the amount of time the `Session` can be inactive before it expires.
<4> We now save the `Session`.
This is why we needed the generic type `S`.
The `SessionRepository` only allows saving `ExpiringSession` instances that were created or retrieved using the same `SessionRepository`.
The `SessionRepository` only allows saving `Session` instances that were created or retrieved using the same `SessionRepository`.
This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
The last accessed time is automatically updated when the `ExpiringSession` is saved.
<5> We retrieve the `ExpiringSession` from the `SessionRepository`.
If the `ExpiringSession` were expired, the result would be null.
The last accessed time is automatically updated when the `Session` is saved.
<5> We retrieve the `Session` from the `SessionRepository`.
If the `Session` were expired, the result would be null.
[[api-sessionrepository]]
=== SessionRepository
@@ -640,7 +636,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:a
[[api-redisoperationssessionrepository-expiration]]
===== Session Expiration
An expiration is associated to each session using the EXPIRE command based upon the `ExpiringSession.getMaxInactiveInterval()`.
An expiration is associated to each session using the EXPIRE command based upon the `Session.getMaxInactiveInterval()`.
For example:
----
@@ -653,7 +649,7 @@ An expiration is set on the session itself five minutes after it actually expire
[NOTE]
====
The `SessionRepository.getSession(String)` method ensures that no expired sessions will be returned.
The `SessionRepository.findById(String)` method ensures that no expired sessions will be returned.
This means there is no need to check the expiration before using a session.
====
@@ -776,7 +772,7 @@ redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed
[[api-mapsessionrepository]]
=== MapSessionRepository
The `MapSessionRepository` allows for persisting `ExpiringSession` in a `Map` with the key being the `ExpiringSession` id and the value being the `ExpiringSession`.
The `MapSessionRepository` allows for persisting `Session` in a `Map` with the key being the `Session` id and the value being the `Session`.
The implementation can be used with a `ConcurrentHashMap` as a testing or convenience mechanism.
Alternatively, it can be used with distributed `Map` implementations. For example, it can be used with Hazelcast.
@@ -860,14 +856,14 @@ For example, with PostgreSQL database you would use the following schema script:
[source,sql,indent=0]
----
include::{session-main-resources-dir}org/springframework/session/jdbc/schema-postgresql.sql[]
include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schema-postgresql.sql[]
----
And with MySQL database:
[source,sql,indent=0]
----
include::{session-main-resources-dir}org/springframework/session/jdbc/schema-mysql.sql[]
include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schema-mysql.sql[]
----
==== Transaction management

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 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.
@@ -21,7 +21,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -38,7 +38,7 @@ import static org.mockito.Mockito.mock;
@WebAppConfiguration
public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests {
@Autowired
SessionRepositoryFilter<? extends ExpiringSession> filter;
SessionRepositoryFilter<? extends Session> filter;
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {

View File

@@ -16,6 +16,8 @@
package docs;
import java.time.Duration;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
@@ -26,7 +28,6 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
@@ -49,7 +50,7 @@ public class IndexDocTests {
@Test
public void repositoryDemo() {
RepositoryDemo<ExpiringSession> demo = new RepositoryDemo<>();
RepositoryDemo<MapSession> demo = new RepositoryDemo<>();
demo.repository = new MapSessionRepository();
demo.demo();
@@ -68,7 +69,7 @@ public class IndexDocTests {
this.repository.save(toSave); // <4>
S session = this.repository.getSession(toSave.getId()); // <5>
S session = this.repository.findById(toSave.getId()); // <5>
// <6>
User user = session.getAttribute(ATTR_USER);
@@ -81,24 +82,24 @@ public class IndexDocTests {
@Test
public void expireRepositoryDemo() {
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<>();
ExpiringRepositoryDemo<MapSession> demo = new ExpiringRepositoryDemo<>();
demo.repository = new MapSessionRepository();
demo.demo();
}
// tag::expire-repository-demo[]
public class ExpiringRepositoryDemo<S extends ExpiringSession> {
public class ExpiringRepositoryDemo<S extends Session> {
private SessionRepository<S> repository; // <1>
public void demo() {
S toSave = this.repository.createSession(); // <2>
// ...
toSave.setMaxInactiveIntervalInSeconds(30); // <3>
toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); // <3>
this.repository.save(toSave); // <4>
S session = this.repository.getSession(toSave.getId()); // <5>
S session = this.repository.findById(toSave.getId()); // <5>
// ...
}
@@ -111,7 +112,7 @@ public class IndexDocTests {
public void newRedisOperationsSessionRepository() {
// tag::new-redisoperationssessionrepository[]
LettuceConnectionFactory factory = new LettuceConnectionFactory();
SessionRepository<? extends ExpiringSession> repository = new RedisOperationsSessionRepository(
SessionRepository<? extends Session> repository = new RedisOperationsSessionRepository(
factory);
// end::new-redisoperationssessionrepository[]
}
@@ -120,7 +121,7 @@ public class IndexDocTests {
@SuppressWarnings("unused")
public void mapRepository() {
// tag::new-mapsessionrepository[]
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
SessionRepository<? extends Session> repository = new MapSessionRepository();
// end::new-mapsessionrepository[]
}
@@ -136,7 +137,7 @@ public class IndexDocTests {
// ... configure transactionManager ...
SessionRepository<? extends ExpiringSession> repository =
SessionRepository<? extends Session> repository =
new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
// end::new-jdbcoperationssessionrepository[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 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,6 +16,8 @@
package docs.http;
import java.util.Properties;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,11 +33,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
* @author Mark Paluch
* @since 1.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -63,6 +67,7 @@ public abstract class AbstractHttpSessionListenerTests {
RedisConnection connection = mock(RedisConnection.class);
given(factory.getConnection()).willReturn(connection);
given(connection.getConfig(anyString())).willReturn(new Properties());
return factory;
}

View File

@@ -16,8 +16,8 @@
package docs.security;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.Cookie;
@@ -26,7 +26,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
@@ -48,7 +48,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@WebAppConfiguration
@SuppressWarnings("rawtypes")
public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
public class RememberMeSecurityConfigurationTests<T extends Session> {
@Autowired
WebApplicationContext context;
@Autowired
@@ -81,9 +81,9 @@ public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.getSession(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(Duration.ofDays(30));
}
}

View File

@@ -16,8 +16,8 @@
package docs.security;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.Cookie;
@@ -26,7 +26,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
@@ -48,7 +48,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("rawtypes")
public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession> {
public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
@Autowired
WebApplicationContext context;
@Autowired
@@ -81,9 +81,9 @@ public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession>
Cookie cookie = result.getResponse().getCookie("SESSION");
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
T session = this.sessions
.getSession(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
assertThat(session.getMaxInactiveInterval())
.isEqualTo(Duration.ofDays(30));
}
}

View File

@@ -21,8 +21,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
/**
@@ -33,7 +33,7 @@ import org.springframework.session.security.SpringSessionBackedSessionRegistry;
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
private FindByIndexNameSessionRepository<Session> sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
@@ -48,8 +48,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry<ExpiringSession>(
this.sessionRepository);
return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
}
}
// end::class[]

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.0.M1
version=2.0.0.M2
springBootVersion=2.0.0.M2
version=2.0.0.M3

View File

@@ -1,8 +1,9 @@
dependencyManagement {
imports {
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RC2'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-M4'
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M2'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-M3'
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RC3'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-RC1'
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M3'
}
dependencies {
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.0.pr3'
@@ -12,7 +13,7 @@ dependencyManagement {
dependency 'com.maxmind.geoip2:geoip2:2.3.1'
dependency 'commons-codec:commons-codec:1.10'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.0.0.M2'
dependency 'io.lettuce:lettuce-core:5.0.0.RC1'
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 'javax.servlet:javax.servlet-api:3.1.0'

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Wed Jan 11 10:54:44 CST 2017
#Fri Jul 07 11:11:13 CDT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip

19
gradlew vendored
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -154,16 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save ( ) {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
exec "$JAVACMD" "$@"

View File

@@ -1,11 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session-data-redis')
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-devtools"
compile "org.springframework.session:spring-session"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"

View File

@@ -0,0 +1,6 @@
dependencyManagement {
dependencies {
dependency 'io.lettuce:lettuce-core:5.0.0.M2'
}
}

View File

@@ -1,11 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session-jdbc')
compile "org.springframework.boot:spring-boot-starter-jdbc"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-devtools"
compile "org.springframework.session:spring-session"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"

View File

@@ -1,13 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-devtools"
compile "org.springframework.session:spring-session"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"

View File

@@ -1,11 +1,12 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session-data-redis')
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-devtools"
compile "org.springframework.session:spring-session"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"

View File

@@ -1,15 +1,14 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-websocket"
compile "org.springframework.boot:spring-boot-devtools"
compile "org.springframework.session:spring-session"
compile "org.springframework:spring-websocket"
compile "org.springframework.security:spring-security-messaging"
compile "org.springframework.security:spring-security-data"

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"

View File

@@ -1,7 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile project(':spring-session')
compile project(':spring-session-hazelcast')
compile "org.springframework:spring-web"
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-web"

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "io.lettuce:lettuce-core"
compile "org.springframework:spring-webmvc"
compile "org.springframework.security:spring-security-config"
@@ -19,4 +17,4 @@ dependencies {
testCompile "org.assertj:assertj-core"
testCompile "org.springframework:spring-test"
testCompile "commons-codec:commons-codec"
}
}

View File

@@ -26,7 +26,7 @@ import sample.mvc.MvcConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -50,7 +50,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class RestMockMvcTests {
@Autowired
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
WebApplicationContext context;

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-web"

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"

View File

@@ -61,7 +61,7 @@ public class UserAccountsFilter implements Filter {
String alias = entry.getKey();
String sessionId = entry.getValue();
Session session = repo.getSession(sessionId);
Session session = repo.findById(sessionId);
if (session == null) {
continue;
}

View File

@@ -1,7 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile project(':spring-session')
compile project(':spring-session-core')
compile "org.webjars:bootstrap"
compile "org.webjars:webjars-taglib"
compile "com.hazelcast:hazelcast-client"

View File

@@ -23,52 +23,35 @@ import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.config.SerializerConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.http.SessionRepositoryFilter;
@WebListener
public class Initializer implements ServletContextListener {
private static final String SESSION_MAP_NAME = "spring:session:sessions";
private HazelcastInstance instance;
public void contextInitialized(ServletContextEvent sce) {
String sessionMapName = "spring:session:sessions";
ServletContext sc = sce.getServletContext();
this.instance = createHazelcastInstance();
Map<String, Session> sessions = this.instance.getMap(SESSION_MAP_NAME);
Config cfg = new Config();
NetworkConfig netConfig = new NetworkConfig();
netConfig.setPort(getAvailablePort());
cfg.setNetworkConfig(netConfig);
SerializerConfig serializer = new SerializerConfig().setTypeClass(Object.class)
.setImplementation(new ObjectStreamSerializer());
cfg.getSerializationConfig().addSerializerConfig(serializer);
MapConfig mc = new MapConfig();
mc.setName(sessionMapName);
mc.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
cfg.addMapConfig(mc);
this.instance = Hazelcast.newHazelcastInstance(cfg);
Map<String, ExpiringSession> sessions = this.instance.getMap(sessionMapName);
SessionRepository<ExpiringSession> sessionRepository = new MapSessionRepository(
sessions);
SessionRepositoryFilter<ExpiringSession> filter = new SessionRepositoryFilter<>(
MapSessionRepository sessionRepository = new MapSessionRepository(sessions);
SessionRepositoryFilter<? extends Session> filter = new SessionRepositoryFilter<>(
sessionRepository);
Dynamic fr = sc.addFilter("springSessionFilter", filter);
Dynamic fr = sce.getServletContext().addFilter("springSessionFilter", filter);
fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
@@ -76,6 +59,17 @@ public class Initializer implements ServletContextListener {
this.instance.shutdown();
}
private HazelcastInstance createHazelcastInstance() {
Config config = new Config();
config.getNetworkConfig().setPort(getAvailablePort());
config.getMapConfig(SESSION_MAP_NAME)
.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
return Hazelcast.newHazelcastInstance(config);
}
private static int getAvailablePort() {
ServerSocket socket = null;
try {
@@ -93,4 +87,5 @@ public class Initializer implements ServletContextListener {
}
}
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2014-2016 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 sample;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.nio.serialization.StreamSerializer;
/**
* A {@link StreamSerializer} that uses Java serialization to persist the session. This is
* certainly not the most efficient way to persist sessions, but the example is intended
* to demonstrate using minimal dependencies. For better serialization methods try using
* <a href="https://github.com/EsotericSoftware/kryo">Kryo</a>.
*
* @author Rob Winch
*
*/
public class ObjectStreamSerializer implements StreamSerializer<Object> {
public int getTypeId() {
return 2;
}
public void write(ObjectDataOutput objectDataOutput, Object object)
throws IOException {
ObjectOutputStream out = new ObjectOutputStream((OutputStream) objectDataOutput);
out.writeObject(object);
out.flush();
}
public Object read(ObjectDataInput objectDataInput) throws IOException {
ObjectInputStream in = new ObjectInputStream((InputStream) objectDataInput);
try {
return in.readObject();
}
catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
public void destroy() {
}
}

View File

@@ -1,9 +1,7 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile(project(':spring-session-data-redis')) {
exclude module: 'jedis'
}
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"

View File

@@ -5,25 +5,18 @@ description = "Spring Session"
dependencies {
compile "org.springframework:spring-jcl"
optional "io.projectreactor:reactor-core"
optional "javax.servlet:javax.servlet-api"
optional "org.springframework:spring-context"
optional "org.springframework:spring-jdbc"
optional "org.springframework:spring-messaging"
optional "org.springframework:spring-web"
optional "org.springframework:spring-webflux"
optional "org.springframework:spring-websocket"
optional "org.springframework.data:spring-data-redis"
optional "org.springframework.security:spring-security-core"
optional "org.springframework.security:spring-security-web"
optional "com.hazelcast:hazelcast"
provided "javax.servlet:javax.servlet-api"
integrationTestCompile "redis.clients:jedis"
integrationTestCompile "org.apache.commons:commons-pool2"
integrationTestCompile "org.apache.derby:derby"
integrationTestCompile "com.h2database:h2"
integrationTestCompile "com.hazelcast:hazelcast-client"
integrationTestCompile "org.hsqldb:hsqldb"
testCompile "io.projectreactor:reactor-test"
testCompile "junit:junit"
testCompile "org.mockito:mockito-core"
testCompile "edu.umd.cs.mtc:multithreadedtc"

View File

@@ -0,0 +1,153 @@
/*
* 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.
* 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;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import reactor.core.publisher.Mono;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
/**
* A {@link SessionRepository} backed by a {@link Map} and that uses a
* {@link MapSession}. By default a {@link ConcurrentHashMap} is
* used, but a custom {@link Map} can be injected to use distributed maps
* provided by NoSQL stores like Redis and Hazelcast.
*
* <p>
* The implementation does NOT support firing {@link SessionDeletedEvent} or
* {@link SessionExpiredEvent}.
* </p>
*
* @author Rob Winch
* @since 2.0
*/
public class MapReactorSessionRepository implements ReactorSessionRepository<MapSession> {
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private final Map<String, Session> sessions;
/**
* Creates an instance backed by a {@link ConcurrentHashMap}.
*/
public MapReactorSessionRepository() {
this(new ConcurrentHashMap<>());
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = sessions;
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Session... sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = new ConcurrentHashMap<>();
for (Session session : sessions) {
this.performSave(new MapSession(session));
}
}
/**
* Creates a new instance backed by the provided {@link Map}. This allows
* injecting a distributed {@link Map}.
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Iterable<Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
this.sessions = new ConcurrentHashMap<>();
for (Session session : sessions) {
this.performSave(new MapSession(session));
}
}
/**
* If non-null, this value is used to override
* {@link Session#setMaxInactiveInterval(Duration)}.
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval);
}
public Mono<Void> save(MapSession session) {
return Mono.fromRunnable(() -> performSave(session));
}
private void performSave(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));
}
public Mono<MapSession> findById(String id) {
return Mono.defer(() -> {
Session saved = this.sessions.get(id);
if (saved == null) {
return Mono.empty();
}
if (saved.isExpired()) {
delete(saved.getId());
return Mono.empty();
}
return Mono.just(new MapSession(saved));
});
}
public Mono<Void> delete(String id) {
return Mono.fromRunnable(() -> this.sessions.remove(id));
}
public Mono<MapSession> createSession() {
return Mono.defer(() -> {
MapSession result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return Mono.just(result);
});
}
}

View File

@@ -17,11 +17,12 @@
package org.springframework.session;
import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* <p>
@@ -41,31 +42,34 @@ import java.util.concurrent.TimeUnit;
* </p>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public final class MapSession implements ExpiringSession, Serializable {
public final class MapSession implements Session, Serializable {
/**
* Default {@link #setMaxInactiveIntervalInSeconds(int)} (30 minutes).
* Default {@link #setMaxInactiveInterval(Duration)} (30 minutes).
*/
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
private String id;
private String originalId;
private Map<String, Object> sessionAttrs = new HashMap<>();
private long creationTime = System.currentTimeMillis();
private long lastAccessedTime = this.creationTime;
private Instant creationTime = Instant.now();
private Instant lastAccessedTime = this.creationTime;
/**
* Defaults to 30 minutes.
*/
private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
private Duration maxInactiveInterval = Duration.ofSeconds(DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
/**
* Creates a new instance with a secure randomly generated identifier.
*/
public MapSession() {
this(UUID.randomUUID().toString());
this(generateId());
}
/**
* Creates a new instance with the specified id. This is preferred to the default
* constructor when the id is known to prevent unnecessary consumption on entropy
@@ -75,6 +79,7 @@ public final class MapSession implements ExpiringSession, Serializable {
*/
public MapSession(String id) {
this.id = id;
this.originalId = id;
}
/**
@@ -83,27 +88,30 @@ public final class MapSession implements ExpiringSession, Serializable {
* @param session the {@link Session} to initialize this {@link Session} with. Cannot
* be null.
*/
public MapSession(ExpiringSession session) {
public MapSession(Session session) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
this.id = session.getId();
this.originalId = this.id;
this.sessionAttrs = new HashMap<>(
session.getAttributeNames().size());
for (String attrName : session.getAttributeNames()) {
Object attrValue = session.getAttribute(attrName);
this.sessionAttrs.put(attrName, attrValue);
if (attrValue != null) {
this.sessionAttrs.put(attrName, attrValue);
}
}
this.lastAccessedTime = session.getLastAccessedTime();
this.creationTime = session.getCreationTime();
this.maxInactiveInterval = session.getMaxInactiveIntervalInSeconds();
this.maxInactiveInterval = session.getMaxInactiveInterval();
}
public void setLastAccessedTime(long lastAccessedTime) {
public void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}
public long getCreationTime() {
public Instant getCreationTime() {
return this.creationTime;
}
@@ -111,28 +119,41 @@ public final class MapSession implements ExpiringSession, Serializable {
return this.id;
}
public long getLastAccessedTime() {
String getOriginalId() {
return this.originalId;
}
void setOriginalId(String originalId) {
this.originalId = originalId;
}
public String changeSessionId() {
String changedId = generateId();
setId(changedId);
return changedId;
}
public Instant getLastAccessedTime() {
return this.lastAccessedTime;
}
public void setMaxInactiveIntervalInSeconds(int interval) {
public void setMaxInactiveInterval(Duration interval) {
this.maxInactiveInterval = interval;
}
public int getMaxInactiveIntervalInSeconds() {
public Duration getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
public boolean isExpired() {
return isExpired(System.currentTimeMillis());
return isExpired(Instant.now());
}
boolean isExpired(long now) {
if (this.maxInactiveInterval < 0) {
boolean isExpired(Instant now) {
if (this.maxInactiveInterval.isNegative()) {
return false;
}
return now - TimeUnit.SECONDS
.toMillis(this.maxInactiveInterval) >= this.lastAccessedTime;
return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
}
@SuppressWarnings("unchecked")
@@ -158,12 +179,11 @@ public final class MapSession implements ExpiringSession, Serializable {
}
/**
* Sets the time that this {@link Session} was created in milliseconds since midnight
* of 1/1/1970 GMT. The default is when the {@link Session} was instantiated.
* @param creationTime the time that this {@link Session} was created in milliseconds
* since midnight of 1/1/1970 GMT.
* Sets the time that this {@link Session} was created. The default is when the
* {@link Session} was instantiated.
* @param creationTime the time that this {@link Session} was created.
*/
public void setCreationTime(long creationTime) {
public void setCreationTime(Instant creationTime) {
this.creationTime = creationTime;
}
@@ -186,5 +206,9 @@ public final class MapSession implements ExpiringSession, Serializable {
return this.id.hashCode();
}
private static String generateId() {
return UUID.randomUUID().toString();
}
private static final long serialVersionUID = 7160779239673823561L;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.session;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -36,14 +37,14 @@ import org.springframework.session.events.SessionExpiredEvent;
* @author Rob Winch
* @since 1.0
*/
public class MapSessionRepository implements SessionRepository<ExpiringSession> {
public class MapSessionRepository implements SessionRepository<MapSession> {
/**
* If non-null, this value is used to override
* {@link ExpiringSession#setMaxInactiveIntervalInSeconds(int)}.
* {@link Session#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private final Map<String, ExpiringSession> sessions;
private final Map<String, Session> sessions;
/**
* Creates an instance backed by a {@link java.util.concurrent.ConcurrentHashMap}.
@@ -58,7 +59,7 @@ public class MapSessionRepository implements SessionRepository<ExpiringSession>
*
* @param sessions the {@link java.util.Map} to use. Cannot be null.
*/
public MapSessionRepository(Map<String, ExpiringSession> sessions) {
public MapSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
@@ -67,7 +68,7 @@ public class MapSessionRepository implements SessionRepository<ExpiringSession>
/**
* If non-null, this value is used to override
* {@link ExpiringSession#setMaxInactiveIntervalInSeconds(int)}.
* {@link Session#setMaxInactiveInterval(Duration)}.
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
* should be kept alive between client requests.
*/
@@ -75,30 +76,35 @@ public class MapSessionRepository implements SessionRepository<ExpiringSession>
this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval);
}
public void save(ExpiringSession session) {
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));
}
public ExpiringSession getSession(String id) {
ExpiringSession saved = this.sessions.get(id);
public MapSession findById(String id) {
Session saved = this.sessions.get(id);
if (saved == null) {
return null;
}
if (saved.isExpired()) {
delete(saved.getId());
deleteById(saved.getId());
return null;
}
return new MapSession(saved);
}
public void delete(String id) {
public void deleteById(String id) {
this.sessions.remove(id);
}
public ExpiringSession createSession() {
ExpiringSession result = new MapSession();
public MapSession createSession() {
MapSession result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval);
result.setMaxInactiveInterval(
Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return result;
}

View File

@@ -0,0 +1,77 @@
/*
* 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.
* 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;
import reactor.core.publisher.Mono;
/**
* A repository interface for managing {@link Session} instances.
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 2.0
*/
public interface ReactorSessionRepository<S extends Session> {
/**
* Creates a new {@link Session} that is capable of being persisted by this
* {@link ReactorSessionRepository}.
*
* <p>
* This allows optimizations and customizations in how the {@link Session} is
* persisted. For example, the implementation returned might keep track of the changes
* ensuring that only the delta needs to be persisted on a save.
* </p>
*
* @return a new {@link Session} that is capable of being persisted by this
* {@link ReactorSessionRepository}
*/
Mono<S> createSession();
/**
* Ensures the {@link Session} created by
* {@link ReactorSessionRepository#createSession()} is saved.
*
* <p>
* Some implementations may choose to save as the {@link Session} is updated by
* returning a {@link Session} that immediately persists any changes. In this case,
* this method may not actually do anything.
* </p>
*
* @param session the {@link Session} to save
* @return indicator of operation completion
*/
Mono<Void> save(S session);
/**
* Gets the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*
* @param id the {@link Session#getId()} to lookup
* @return the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*/
Mono<S> findById(String id);
/**
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
* if the {@link Session} is not found.
* @param id the {@link Session#getId()} to delete
* @return indicator of operation completion
*/
Mono<Void> delete(String id);
}

View File

@@ -0,0 +1,160 @@
/*
* 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.
* 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;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
/**
* Provides a way to identify a user in an agnostic way. This allows the session to be
* used by an HttpSession, WebSocket Session, or even non web related sessions.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public interface Session {
/**
* Gets a unique string that identifies the {@link Session}.
*
* @return a unique string that identifies the {@link Session}
*/
String getId();
/**
* Changes the session id. After invoking the {@link #getId()} will return a new identifier.
* @return the new session id which {@link #getId()} will now return
*/
String changeSessionId();
/**
* Gets the Object associated with the specified name or null if no Object is
* associated to that name.
*
* @param <T> The return type of the attribute
* @param attributeName the name of the attribute to get
* @return the Object associated with the specified name or null if no Object is
* associated to that name
*/
<T> T getAttribute(String attributeName);
/**
* Return the session attribute value or if not present raise an
* {@link IllegalArgumentException}.
* @param name the attribute name
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T result = getAttribute(name);
if (result == null) {
throw new IllegalArgumentException(
"Required attribute '" + name + "' is missing.");
}
return result;
}
/**
* Return the session attribute value, or a default, fallback value.
* @param name the attribute name
* @param defaultValue a default value to return instead
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
T result = getAttribute(name);
return result == null ? defaultValue : result;
}
/**
* Gets the attribute names that have a value associated with it. Each value can be
* passed into {@link org.springframework.session.Session#getAttribute(String)} to
* obtain the attribute value.
*
* @return the attribute names that have a value associated with it.
* @see #getAttribute(String)
*/
Set<String> getAttributeNames();
/**
* Sets the attribute value for the provided attribute name. If the attributeValue is
* null, it has the same result as removing the attribute with
* {@link org.springframework.session.Session#removeAttribute(String)} .
*
* @param attributeName the attribute name to set
* @param attributeValue the value of the attribute to set. If null, the attribute
* will be removed.
*/
void setAttribute(String attributeName, Object attributeValue);
/**
* Removes the attribute with the provided attribute name.
* @param attributeName the name of the attribute to remove
*/
void removeAttribute(String attributeName);
/**
* Gets the time when this session was created.
*
* @return the time when this session was created.
*/
Instant getCreationTime();
/**
* Sets the last accessed time.
*
* @param lastAccessedTime the last accessed time
*/
void setLastAccessedTime(Instant lastAccessedTime);
/**
* Gets the last time this {@link Session} was accessed.
*
* @return the last time the client sent a request associated with the session
*/
Instant getLastAccessedTime();
/**
* Sets the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*
* @param interval the amount of time that the {@link Session} should be kept alive
* between client requests.
*/
void setMaxInactiveInterval(Duration interval);
/**
* Gets the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*
* @return the maximum inactive interval between requests before this session will be
* invalidated. A negative time indicates that the session will never timeout.
*/
Duration getMaxInactiveInterval();
/**
* Returns true if the session is expired.
*
* @return true if the session is expired, else false.
*/
boolean isExpired();
}

View File

@@ -62,12 +62,12 @@ public interface SessionRepository<S extends Session> {
* @return the {@link Session} by the {@link Session#getId()} or null if no
* {@link Session} is found.
*/
S getSession(String id);
S findById(String id);
/**
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
* if the {@link Session} is not found.
* @param id the {@link org.springframework.session.Session#getId()} to delete
*/
void delete(String id);
void deleteById(String id);
}

View File

@@ -33,7 +33,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
@@ -122,7 +122,7 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
}
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);

View File

@@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@@ -32,12 +31,12 @@ import org.springframework.session.SessionRepository;
* Ensures that calling {@link #expireNow()} propagates to Spring Session, since this
* session information contains only derived data and is not the authoritative source.
*
* @param <S> the {@link ExpiringSession} type.
* @param <S> the {@link Session} type.
* @author Joris Kuipers
* @author Vedran Pavic
* @since 1.3
*/
class SpringSessionBackedSessionInformation<S extends ExpiringSession>
class SpringSessionBackedSessionInformation<S extends Session>
extends SessionInformation {
static final String EXPIRED_ATTR = SpringSessionBackedSessionInformation.class
@@ -53,9 +52,10 @@ class SpringSessionBackedSessionInformation<S extends ExpiringSession>
SpringSessionBackedSessionInformation(S session,
SessionRepository<S> sessionRepository) {
super(resolvePrincipal(session), session.getId(),
new Date(session.getLastAccessedTime()));
Date.from(session.getLastAccessedTime()));
this.sessionRepository = sessionRepository;
if (Boolean.TRUE.equals(session.getAttribute(EXPIRED_ATTR))) {
Boolean expired = session.getAttribute(EXPIRED_ATTR);
if (Boolean.TRUE.equals(expired)) {
super.expireNow();
}
}
@@ -72,8 +72,10 @@ class SpringSessionBackedSessionInformation<S extends ExpiringSession>
if (principalName != null) {
return principalName;
}
SecurityContext securityContext = session.getAttribute(SPRING_SECURITY_CONTEXT);
if (securityContext != null && securityContext.getAuthentication() != null) {
SecurityContext securityContext = session
.getAttribute(SPRING_SECURITY_CONTEXT);
if (securityContext != null
&& securityContext.getAuthentication() != null) {
return securityContext.getAuthentication().getName();
}
return "";
@@ -87,7 +89,7 @@ class SpringSessionBackedSessionInformation<S extends ExpiringSession>
+ "sessions was exceeded");
}
super.expireNow();
S session = this.sessionRepository.getSession(getSessionId());
S session = this.sessionRepository.findById(getSessionId());
if (session != null) {
session.setAttribute(EXPIRED_ATTR, Boolean.TRUE);
this.sessionRepository.save(session);

View File

@@ -24,8 +24,8 @@ import java.util.List;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
/**
@@ -39,12 +39,12 @@ import org.springframework.util.Assert;
* <p>
* Does not support {@link #getAllPrincipals()}, since that information is not available.
*
* @param <S> the {@link ExpiringSession} type.
* @param <S> the {@link Session} type.
* @author Joris Kuipers
* @author Vedran Pavic
* @since 1.3
*/
public class SpringSessionBackedSessionRegistry<S extends ExpiringSession>
public class SpringSessionBackedSessionRegistry<S extends Session>
implements SessionRegistry {
private final FindByIndexNameSessionRepository<S> sessionRepository;
@@ -70,7 +70,7 @@ public class SpringSessionBackedSessionRegistry<S extends ExpiringSession>
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session
.getAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR))) {
infos.add(new SpringSessionBackedSessionInformation<S>(session,
infos.add(new SpringSessionBackedSessionInformation<>(session,
this.sessionRepository));
}
}
@@ -78,9 +78,9 @@ public class SpringSessionBackedSessionRegistry<S extends ExpiringSession>
}
public SessionInformation getSessionInformation(String sessionId) {
S session = this.sessionRepository.getSession(sessionId);
S session = this.sessionRepository.findById(sessionId);
if (session != null) {
return new SpringSessionBackedSessionInformation<S>(session,
return new SpringSessionBackedSessionInformation<>(session,
this.sessionRepository);
}
return null;

View File

@@ -33,7 +33,6 @@ import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.session.Session;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.util.Assert;
/**
* A {@link HttpSessionStrategy} that uses a cookie to obtain the session from.
@@ -121,7 +120,7 @@ import org.springframework.util.Assert;
* entry.getValue();
* </code>
*
* Session session = repo.getSession(sessionId); if(session == null) { continue; }
* Session session = repo.findById(sessionId); if(session == null) { continue; }
*
* String username = session.getAttribute("username"); if(username == null) {
* newSessionAlias = alias; continue; }
@@ -293,7 +292,9 @@ public final class CookieHttpSessionStrategy
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
*/
public void setCookieSerializer(CookieSerializer cookieSerializer) {
Assert.notNull(cookieSerializer, "cookieSerializer cannot be null");
if (cookieSerializer == null) {
throw new IllegalArgumentException("cookieSerializer cannot be null");
}
this.cookieSerializer = cookieSerializer;
}

View File

@@ -20,7 +20,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.session.Session;
import org.springframework.util.Assert;
/**
* A {@link HttpSessionStrategy} that uses a header to obtain the session from.
@@ -79,7 +78,9 @@ public class HeaderHttpSessionStrategy implements HttpSessionStrategy {
* @param headerName the name of the header to obtain the session id from.
*/
public void setHeaderName(String headerName) {
Assert.notNull(headerName, "headerName cannot be null");
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
this.headerName = headerName;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 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,6 +16,7 @@
package org.springframework.session.web.http;
import java.time.Duration;
import java.util.Collections;
import java.util.Enumeration;
import java.util.NoSuchElementException;
@@ -25,23 +26,23 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
/**
* Adapts Spring Session's {@link ExpiringSession} to an {@link HttpSession}.
* Adapts Spring Session's {@link Session} to an {@link HttpSession}.
*
* @param <S> the {@link ExpiringSession} type
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 1.1
*/
@SuppressWarnings("deprecation")
class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSession {
class HttpSessionAdapter<S extends Session> implements HttpSession {
private S session;
private final ServletContext servletContext;
private boolean invalidated;
private boolean old;
ExpiringSessionHttpSession(S session, ServletContext servletContext) {
HttpSessionAdapter(S session, ServletContext servletContext) {
this.session = session;
this.servletContext = servletContext;
}
@@ -56,7 +57,7 @@ class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSessi
public long getCreationTime() {
checkState();
return this.session.getCreationTime();
return this.session.getCreationTime().toEpochMilli();
}
public String getId() {
@@ -65,7 +66,7 @@ class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSessi
public long getLastAccessedTime() {
checkState();
return this.session.getLastAccessedTime();
return this.session.getLastAccessedTime().toEpochMilli();
}
public ServletContext getServletContext() {
@@ -73,11 +74,11 @@ class ExpiringSessionHttpSession<S extends ExpiringSession> implements HttpSessi
}
public void setMaxInactiveInterval(int interval) {
this.session.setMaxInactiveIntervalInSeconds(interval);
this.session.setMaxInactiveInterval(Duration.ofSeconds(interval));
}
public int getMaxInactiveInterval() {
return this.session.getMaxInactiveIntervalInSeconds();
return (int) this.session.getMaxInactiveInterval().getSeconds();
}
public HttpSessionContext getSessionContext() {

View File

@@ -24,7 +24,7 @@ import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.springframework.context.ApplicationListener;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
@@ -73,8 +73,8 @@ public class SessionEventHttpSessionListenerAdapter
}
private HttpSessionEvent createHttpSessionEvent(AbstractSessionEvent event) {
ExpiringSession session = event.getSession();
HttpSession httpSession = new ExpiringSessionHttpSession<>(session,
Session session = event.getSession();
HttpSession httpSession = new HttpSessionAdapter<>(session,
this.context);
HttpSessionEvent httpSessionEvent = new HttpSessionEvent(httpSession);
return httpSessionEvent;

View File

@@ -17,9 +17,7 @@
package org.springframework.session.web.http;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.time.Instant;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
@@ -33,7 +31,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.Order;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@@ -56,7 +53,7 @@ import org.springframework.session.SessionRepository;
* <li>The session id is looked up using
* {@link HttpSessionStrategy#getRequestedSessionId(javax.servlet.http.HttpServletRequest)}
* . The default is to look in a cookie named SESSION.</li>
* <li>The session id of newly created {@link org.springframework.session.ExpiringSession}
* <li>The session id of newly created {@link org.springframework.session.Session}
* is sent to the client using
* <li>The client is notified that the session id is no longer valid with
* {@link HttpSessionStrategy#onInvalidateSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
@@ -69,12 +66,12 @@ import org.springframework.session.SessionRepository;
* persisted properly.
* </p>
*
* @param <S> the {@link ExpiringSession} type.
* @param <S> the {@link Session} type.
* @since 1.0
* @author Rob Winch
*/
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession>
public class SessionRepositoryFilter<S extends Session>
extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER");
@@ -274,30 +271,7 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
"Cannot change session ID. There is no session associated with this request.");
}
// eagerly get session attributes in case implementation lazily loads them
Map<String, Object> attrs = new HashMap<>();
Enumeration<String> iAttrNames = session.getAttributeNames();
while (iAttrNames.hasMoreElements()) {
String attrName = iAttrNames.nextElement();
Object value = session.getAttribute(attrName);
attrs.put(attrName, value);
}
SessionRepositoryFilter.this.sessionRepository.delete(session.getId());
HttpSessionWrapper original = getCurrentSession();
setCurrentSession(null);
HttpSessionWrapper newSession = getSession();
original.setSession(newSession.getSession());
newSession.setMaxInactiveInterval(session.getMaxInactiveInterval());
for (Map.Entry<String, Object> attr : attrs.entrySet()) {
String attrName = attr.getKey();
Object attrValue = attr.getValue();
newSession.setAttribute(attrName, attrValue);
}
return newSession.getId();
return getCurrentSession().getSession().changeSessionId();
}
@Override
@@ -324,11 +298,11 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
private S getSession(String sessionId) {
S session = SessionRepositoryFilter.this.sessionRepository
.getSession(sessionId);
.findById(sessionId);
if (session == null) {
return null;
}
session.setLastAccessedTime(System.currentTimeMillis());
session.setLastAccessedTime(Instant.now());
return session;
}
@@ -370,7 +344,7 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
"For debugging purposes only (not an error)"));
}
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(System.currentTimeMillis());
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
@@ -402,7 +376,7 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
* @author Rob Winch
* @since 1.0
*/
private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> {
private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
HttpSessionWrapper(S session, ServletContext servletContext) {
super(session, servletContext);
@@ -413,7 +387,7 @@ public class SessionRepositoryFilter<S extends ExpiringSession>
super.invalidate();
SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
setCurrentSession(null);
SessionRepositoryFilter.this.sessionRepository.delete(getId());
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
}
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.
* 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.server.session;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.session.ReactorSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.CookieWebSessionIdResolver;
import org.springframework.web.server.session.WebSessionIdResolver;
import org.springframework.web.server.session.WebSessionManager;
import org.springframework.web.server.session.WebSessionStore;
/**
* The {@link WebSessionManager} implementation backed by
* {@link ReactorSessionRepository}.
*
* @author Rob Winch
* @since 2.0
*/
public class SpringSessionWebSessionManager implements WebSessionManager {
private final SpringSessionWebSessionStore<? extends Session> sessionStore;
private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
private Clock clock = Clock.system(ZoneOffset.UTC);
public SpringSessionWebSessionManager(
ReactorSessionRepository<? extends Session> sessionRepository) {
this.sessionStore = new SpringSessionWebSessionStore<>(sessionRepository);
}
/**
* Return the configured {@link WebSessionIdResolver}.
* @return the configured {@link WebSessionIdResolver}
*/
private WebSessionIdResolver getSessionIdResolver() {
return this.sessionIdResolver;
}
/**
* Configure the id resolution strategy.
* <p>
* By default an instance of {@link CookieWebSessionIdResolver}.
* @param sessionIdResolver the resolver to use
*/
public void setSessionIdResolver(WebSessionIdResolver sessionIdResolver) {
Assert.notNull(sessionIdResolver, "WebSessionIdResolver is required.");
this.sessionIdResolver = sessionIdResolver;
}
/**
* Return the configured {@link WebSessionStore}.
* @return the configured {@link WebSessionStore}
*/
private WebSessionStore getSessionStore() {
return this.sessionStore;
}
/**
* Return the configured clock for session {@code lastAccessTime} calculations.
* @return the configured clock for session {@code lastAccessTime} calculations
*/
private Clock getClock() {
return this.clock;
}
/**
* Configure the {@link Clock} to use to set lastAccessTime on every created session
* and to calculate if it is expired.
* <p>
* This may be useful to align to different timezone or to set the clock back in a
* test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))} in order to
* simulate session expiration.
* <p>
* By default this is {@code Clock.system(ZoneOffset.UTC)}.
* @param clock the clock to use
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "'clock' is required.");
this.clock = clock;
}
@Override
public Mono<WebSession> getSession(ServerWebExchange exchange) {
// @formatter:off
return Mono.defer(() ->
retrieveSession(exchange)
.flatMap(session -> removeSessionIfExpired(exchange, session))
.flatMap(session -> {
Instant lastAccessTime = Instant.now(getClock());
return this.sessionStore.setLastAccessedTime(session, lastAccessTime);
})
.switchIfEmpty(createSession(exchange))
.doOnNext(session -> exchange.getResponse().beforeCommit(session::save)));
// @formatter:on
}
private Mono<WebSession> retrieveSession(ServerWebExchange exchange) {
// @formatter:off
return Flux.fromIterable(getSessionIdResolver().resolveSessionIds(exchange))
.concatMap(this.sessionStore::retrieveSession)
.cast(WebSession.class)
.next();
// @formatter:on
}
private Mono<WebSession> removeSessionIfExpired(ServerWebExchange exchange,
WebSession session) {
if (session.isExpired()) {
this.sessionIdResolver.expireSession(exchange);
return this.sessionStore.removeSession(session.getId()).then(Mono.empty());
}
return Mono.just(session);
}
private Mono<Void> saveSession(ServerWebExchange exchange, WebSession session) {
if (session.isExpired()) {
return Mono.error(new IllegalStateException(
"Sessions are checked for expiration and have their "
+ "lastAccessTime updated when first accessed during request processing. "
+ "However this session is expired meaning that maxIdleTime elapsed "
+ "before the call to session.save()."));
}
if (!session.isStarted()) {
return Mono.empty();
}
// Force explicit start
session.start();
if (hasNewSessionId(exchange, session)) {
this.sessionIdResolver.setSessionId(exchange, session.getId());
}
return this.sessionStore.storeSession(session);
}
private boolean hasNewSessionId(ServerWebExchange exchange, WebSession session) {
List<String> ids = getSessionIdResolver().resolveSessionIds(exchange);
return ids.isEmpty() || !session.getId().equals(ids.get(0));
}
private Mono<WebSession> createSession(ServerWebExchange exchange) {
return this.sessionStore.createSession();
}
}

View File

@@ -0,0 +1,324 @@
/*
* 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.
* 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.server.session;
import java.time.Duration;
import java.time.Instant;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
import org.springframework.session.ReactorSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionStore;
/**
* The {@link WebSessionStore} implementation that provides the {@link WebSession}
* implementation backed by a {@link Session} returned by the
* {@link ReactorSessionRepository}.
*
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 2.0
*/
class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {
private final ReactorSessionRepository<S> sessions;
SpringSessionWebSessionStore(ReactorSessionRepository<S> sessions) {
Assert.notNull(sessions, "sessions cannot be null");
this.sessions = sessions;
}
public Mono<WebSession> createSession() {
return this.sessions.createSession().map(this::createSession);
}
public Mono<WebSession> setLastAccessedTime(WebSession session,
Instant lastAccessedTime) {
@SuppressWarnings("unchecked")
SpringSessionWebSession springSessionWebSession = (SpringSessionWebSession) session;
springSessionWebSession.session.setLastAccessedTime(lastAccessedTime);
return Mono.just(session);
}
@Override
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).map(this::existingSession);
}
@Override
public Mono<Void> changeSessionId(String s, WebSession webSession) {
return storeSession(webSession);
}
private SpringSessionWebSession createSession(S session) {
return new SpringSessionWebSession(session, State.NEW);
}
private SpringSessionWebSession existingSession(S session) {
return new SpringSessionWebSession(session, State.STARTED);
}
@Override
public Mono<Void> removeSession(String sessionId) {
return this.sessions.delete(sessionId);
}
private enum State {
NEW, STARTED
}
private static class SpringSessionMap implements Map<String, Object> {
private final Session session;
private final Collection<Object> values = new SessionValues();
SpringSessionMap(Session session) {
this.session = session;
}
@Override
public int size() {
return this.session.getAttributeNames().size();
}
@Override
public boolean isEmpty() {
return this.session.getAttributeNames().isEmpty();
}
@Override
public boolean containsKey(Object key) {
return key instanceof String
&& this.session.getAttributeNames().contains(key);
}
@Override
public boolean containsValue(Object value) {
return this.session.getAttributeNames().stream()
.anyMatch(attrName -> this.session.getAttribute(attrName) != null);
}
@Override
@Nullable
public Object get(Object key) {
if (key instanceof String) {
return this.session.getAttribute((String) key);
}
return null;
}
@Override
public Object put(String key, Object value) {
Object original = this.session.getAttribute(key);
this.session.setAttribute(key, value);
return original;
}
@Override
@Nullable
public Object remove(Object key) {
if (key instanceof String) {
String attrName = (String) key;
Object original = this.session.getAttribute(attrName);
this.session.removeAttribute(attrName);
return original;
}
return null;
}
@Override
public void putAll(Map<? extends String, ?> m) {
for (Entry<? extends String, ?> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
for (String attrName : this.session.getAttributeNames()) {
remove(attrName);
}
}
@Override
public Set<String> keySet() {
return this.session.getAttributeNames();
}
@Override
public Collection<Object> values() {
return this.values;
}
@Override
public Set<Entry<String, Object>> entrySet() {
Set<String> attrNames = keySet();
Set<Entry<String, Object>> entries = new HashSet<>(attrNames.size());
for (String attrName : attrNames) {
Object value = this.session.getAttribute(attrName);
entries.add(new AbstractMap.SimpleEntry<>(attrName, value));
}
return Collections.unmodifiableSet(entries);
}
private class SessionValues extends AbstractCollection<Object> {
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private Iterator<Entry<String, Object>> i = entrySet().iterator();
public boolean hasNext() {
return this.i.hasNext();
}
public Object next() {
return this.i.next().getValue();
}
public void remove() {
this.i.remove();
}
};
}
public int size() {
return SpringSessionMap.this.size();
}
public boolean isEmpty() {
return SpringSessionMap.this.isEmpty();
}
public void clear() {
SpringSessionMap.this.clear();
}
public boolean contains(Object v) {
return SpringSessionMap.this.containsValue(v);
}
}
}
/**
* Adapts Spring Session's {@link Session} to a {@link WebSession}.
*/
private class SpringSessionWebSession implements WebSession {
private final S session;
private final Map<String, Object> attributes;
private AtomicReference<State> state = new AtomicReference<>();
private volatile transient Supplier<Mono<Void>> saveOperation = Mono::empty;
SpringSessionWebSession(S session, State state) {
Assert.notNull(session, "session cannot be null");
this.session = session;
this.attributes = new SpringSessionMap(session);
this.state.set(state);
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public Mono<Void> changeSessionId() {
return Mono.defer(() -> {
this.session.changeSessionId();
return save();
});
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
@Override
public void start() {
this.state.compareAndSet(State.NEW, State.STARTED);
}
@Override
public boolean isStarted() {
State value = this.state.get();
return (State.STARTED.equals(value)
|| (State.NEW.equals(value) && !getAttributes().isEmpty()));
}
@Override
public Mono<Void> save() {
return this.saveOperation.get();
}
@Override
public boolean isExpired() {
return this.session.isExpired();
}
@Override
public Instant getCreationTime() {
return this.session.getCreationTime();
}
@Override
public Instant getLastAccessTime() {
return this.session.getLastAccessedTime();
}
@Override
public Duration getMaxIdleTime() {
return this.session.getMaxInactiveInterval();
}
@Override
public void setMaxIdleTime(Duration maxIdleTime) {
this.session.setMaxInactiveInterval(maxIdleTime);
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
* 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.
*/
/**
* Spring Session reactive web support.
*/
@NonNullApi
package org.springframework.session.web.server.session;
import org.springframework.lang.NonNullApi;

View File

@@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.socket.handler.WebSocketConnectHandlerDecoratorFactory;
@@ -55,7 +54,7 @@ import org.springframework.web.util.UrlPathHelper;
* {@literal @Configuration}
* {@literal @EnableScheduling}
* {@literal @EnableWebSocketMessageBroker}
* {@literal public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebSocketMessageBrokerConfigurer<S>} {
* {@literal public class WebSocketConfig<S extends Session> extends AbstractSessionWebSocketMessageBrokerConfigurer<S>} {
*
* {@literal @Override}
* protected void configureStompEndpoints(StompEndpointRegistry registry) {
@@ -71,11 +70,11 @@ import org.springframework.web.util.UrlPathHelper;
* }
* </code>
*
* @param <S> the type of ExpiringSession
* @param <S> the type of Session
* @author Rob Winch
* @since 1.0
*/
public abstract class AbstractSessionWebSocketMessageBrokerConfigurer<S extends ExpiringSession>
public abstract class AbstractSessionWebSocketMessageBrokerConfigurer<S extends Session>
extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 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,6 +16,7 @@
package org.springframework.session.web.socket.server;
import java.time.Instant;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
@@ -31,7 +32,6 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
@@ -41,12 +41,12 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* <p>
* Acts as a {@link ChannelInterceptor} and a {@link HandshakeInterceptor} to ensure the
* {@link ExpiringSession#getLastAccessedTime()} is up to date.
* {@link Session#getLastAccessedTime()} is up to date.
* </p>
* <ul>
* <li>Associates the {@link Session#getId()} with the WebSocket Session attributes when
* the handshake is performed. This is later used when intercepting messages to ensure the
* {@link ExpiringSession#getLastAccessedTime()} is updated.</li>
* {@link Session#getLastAccessedTime()} is updated.</li>
* <li>Intercepts {@link Message}'s that are have {@link SimpMessageType} that corresponds
* to {@link #setMatchingMessageTypes(Set)} and updates the last accessed time of the
* {@link Session}. If the {@link Session} is expired, the {@link Message} is prevented
@@ -58,11 +58,11 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
* {@link ChannelInterceptor} and a {@link HandshakeInterceptor} .
* </p>
*
* @param <S> the {@link ExpiringSession} type
* @param <S> the {@link Session} type
* @author Rob Winch
* @since 1.0
*/
public final class SessionRepositoryMessageInterceptor<S extends ExpiringSession>
public final class SessionRepositoryMessageInterceptor<S extends Session>
extends ChannelInterceptorAdapter implements HandshakeInterceptor {
private static final String SPRING_SESSION_ID_ATTR_NAME = "SPRING.SESSION.ID";
@@ -88,7 +88,7 @@ public final class SessionRepositoryMessageInterceptor<S extends ExpiringSession
* <p>
* Sets the {@link SimpMessageType} to match on. If the {@link Message} matches, then
* {@link #preSend(Message, MessageChannel)} ensures the {@link Session} is not
* expired and updates the {@link ExpiringSession#getLastAccessedTime()}
* expired and updates the {@link Session#getLastAccessedTime()}
* </p>
*
* <p>
@@ -121,10 +121,10 @@ public final class SessionRepositoryMessageInterceptor<S extends ExpiringSession
String sessionId = sessionHeaders == null ? null
: (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME);
if (sessionId != null) {
S session = this.sessionRepository.getSession(sessionId);
S session = this.sessionRepository.findById(sessionId);
if (session != null) {
// update the last accessed time
session.setLastAccessedTime(System.currentTimeMillis());
session.setLastAccessedTime(Instant.now());
this.sessionRepository.save(session);
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.
* 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;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 2.0
*/
public class MapReactorSessionRepositoryTests {
MapReactorSessionRepository repository;
MapSession session;
@Before
public void setup() {
this.repository = new MapReactorSessionRepository();
this.session = new MapSession("session-id");
}
@Test
public void constructorVarargsThenFound() {
this.repository = new MapReactorSessionRepository(this.session);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorVarargsWhenNullThenThrowsIllegalArgumentException() {
Session[] sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void constructorIterableThenFound() {
this.repository = new MapReactorSessionRepository(Arrays.asList(this.session));
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorIterableWhenNullThenThrowsIllegalArgumentException() {
Iterable<Session> sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void constructorMapThenFound() {
Map<String, Session> sessions = new HashMap<>();
sessions.put(this.session.getId(), this.session);
this.repository = new MapReactorSessionRepository(sessions);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test(expected = IllegalArgumentException.class)
public void constructorMapWhenNullThenThrowsIllegalArgumentException() {
Map<String, Session> sessions = null;
new MapReactorSessionRepository(sessions);
}
@Test
public void saveWhenNoSubscribersThenNotFound() {
this.repository.save(this.session);
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
}
@Test
public void saveWhenSubscriberThenFound() {
this.repository.save(this.session).block();
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void findByIdWhenNotExpiredThenFound() {
this.repository = new MapReactorSessionRepository(this.session);
Session findByIdSession = this.repository.findById(this.session.getId()).block();
assertThat(findByIdSession).isNotNull();
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
}
@Test
public void findByIdWhenExpiredThenEmpty() {
this.session.setMaxInactiveInterval(Duration.ofSeconds(1));
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
this.repository = new MapReactorSessionRepository(Arrays.asList(this.session));
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
}
@Test
public void createSessionWhenDefaultMaxInactiveIntervalThenDefaultMaxInactiveInterval() {
Session session = this.repository.createSession().block();
assertThat(session).isInstanceOf(MapSession.class);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
}
@Test
public void createSessionWhenCustomMaxInactiveIntervalThenCustomMaxInactiveInterval() {
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
.plusSeconds(10);
this.repository.setDefaultMaxInactiveInterval(
(int) expectedMaxInterval.getSeconds());
Session session = this.repository.createSession().block();
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expectedMaxInterval);
}
@Test
public void changeSessionIdWhenNotYetSaved() {
MapSession createSession = this.repository.createSession().block();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession).block();
assertThat(this.repository.findById(originalId).block()).isNull();
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
}
@Test
public void changeSessionIdWhenSaved() {
MapSession createSession = this.repository.createSession().block();
this.repository.save(createSession).block();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession).block();
assertThat(this.repository.findById(originalId).block()).isNull();
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.
* 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;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class MapSessionRepositoryTests {
MapSessionRepository repository;
MapSession session;
@Before
public void setup() {
this.repository = new MapSessionRepository();
this.session = new MapSession();
}
@Test
public void getSessionExpired() {
this.session.setMaxInactiveInterval(Duration.ofSeconds(1));
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
this.repository.save(this.session);
assertThat(this.repository.findById(this.session.getId())).isNull();
}
@Test
public void createSessionDefaultExpiration() {
Session session = this.repository.createSession();
assertThat(session).isInstanceOf(MapSession.class);
assertThat(session.getMaxInactiveInterval())
.isEqualTo(new MapSession().getMaxInactiveInterval());
}
@Test
public void createSessionCustomDefaultExpiration() {
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
.plusSeconds(10);
this.repository.setDefaultMaxInactiveInterval(
(int) expectedMaxInterval.getSeconds());
Session session = this.repository.createSession();
assertThat(session.getMaxInactiveInterval())
.isEqualTo(expectedMaxInterval);
}
@Test
public void changeSessionIdWhenNotYetSaved() {
MapSession createSession = this.repository.createSession();
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(createSession.getId())).isNotNull();
}
@Test
public void changeSessionIdWhenSaved() {
MapSession createSession = this.repository.createSession();
this.repository.save(createSession);
String originalId = createSession.getId();
createSession.changeSessionId();
this.repository.save(createSession);
assertThat(this.repository.findById(originalId)).isNull();
assertThat(this.repository.findById(createSession.getId())).isNotNull();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 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,6 +16,8 @@
package org.springframework.session;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import org.junit.Before;
@@ -30,12 +32,53 @@ public class MapSessionTests {
@Before
public void setup() {
this.session = new MapSession();
this.session.setLastAccessedTime(1413258262962L);
this.session.setLastAccessedTime(Instant.ofEpochMilli(1413258262962L));
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullSession() {
new MapSession((ExpiringSession) null);
new MapSession((Session) null);
}
@Test
public void getAttributeWhenNullThenNull() {
String result = this.session.getAttribute("attrName");
assertThat(result).isNull();
}
@Test
public void getAttributeOrDefaultWhenNullThenDefaultValue() {
String defaultValue = "default";
String result = this.session.getAttributeOrDefault("attrName", defaultValue);
assertThat(result).isEqualTo(defaultValue);
}
@Test
public void getAttributeOrDefaultWhenNotNullThenDefaultValue() {
String defaultValue = "default";
String attrValue = "value";
String attrName = "attrName";
this.session.setAttribute(attrName, attrValue);
String result = this.session.getAttributeOrDefault(attrName, defaultValue);
assertThat(result).isEqualTo(attrValue);
}
@Test(expected = IllegalArgumentException.class)
public void getRequiredAttributeWhenNullThenException() {
this.session.getRequiredAttribute("attrName");
}
@Test
public void getRequiredAttributeWhenNotNullThenReturns() {
String attrValue = "value";
String attrName = "attrName";
this.session.setAttribute(attrName, attrValue);
String result = this.session.getRequiredAttribute("attrName");
assertThat(result).isEqualTo(attrValue);
}
/**
@@ -69,46 +112,50 @@ public class MapSessionTests {
@Test
public void isExpiredExact() {
long now = 1413260062962L;
Instant now = Instant.ofEpochMilli(1413260062962L);
assertThat(this.session.isExpired(now)).isTrue();
}
@Test
public void isExpiredOneMsTooSoon() {
long now = 1413260062961L;
Instant now = Instant.ofEpochMilli(1413260062961L);
assertThat(this.session.isExpired(now)).isFalse();
}
@Test
public void isExpiredOneMsAfter() {
long now = 1413260062963L;
Instant now = Instant.ofEpochMilli(1413260062963L);
assertThat(this.session.isExpired(now)).isTrue();
}
static class CustomSession implements ExpiringSession {
static class CustomSession implements Session {
public long getCreationTime() {
return 0;
public Instant getCreationTime() {
return Instant.EPOCH;
}
public String changeSessionId() {
throw new UnsupportedOperationException();
}
public String getId() {
return "id";
}
public void setLastAccessedTime(long lastAccessedTime) {
public void setLastAccessedTime(Instant lastAccessedTime) {
throw new UnsupportedOperationException();
}
public long getLastAccessedTime() {
return 0;
public Instant getLastAccessedTime() {
return Instant.EPOCH;
}
public void setMaxInactiveIntervalInSeconds(int interval) {
public void setMaxInactiveInterval(Duration interval) {
}
public int getMaxInactiveIntervalInSeconds() {
return 0;
public Duration getMaxInactiveInterval() {
return Duration.ZERO;
}
public <T> T getAttribute(String attributeName) {

View File

@@ -34,8 +34,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.session.web.http.SessionRepositoryFilter;
@@ -65,7 +65,7 @@ public class EnableSpringHttpSessionCustomCookieSerializerTests {
MockFilterChain chain;
@Autowired
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
CookieSerializer cookieSerializer;

View File

@@ -29,8 +29,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
@@ -58,7 +58,7 @@ public class EnableSpringHttpSessionCustomMultiHttpSessionStrategyTests {
MockFilterChain chain;
@Autowired
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
MultiHttpSessionStrategy strategy;

View File

@@ -16,8 +16,8 @@
package org.springframework.session.security;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -30,13 +30,12 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.User;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.mock;
@@ -56,42 +55,42 @@ public class SpringSessionBackedSessionRegistryTest {
private static final String USER_NAME = "userName";
private static final User PRINCIPAL = new User(USER_NAME, "password",
Collections.<GrantedAuthority>emptyList());
Collections.emptyList());
private static final Date NOW = new Date();
private static final Instant NOW = Instant.now();
@Mock
private FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
private FindByIndexNameSessionRepository<Session> sessionRepository;
@InjectMocks
private SpringSessionBackedSessionRegistry<ExpiringSession> sessionRegistry;
private SpringSessionBackedSessionRegistry<Session> sessionRegistry;
@Test
public void sessionInformationForExistingSession() {
ExpiringSession session = createSession(SESSION_ID, USER_NAME, NOW.getTime());
when(this.sessionRepository.getSession(SESSION_ID)).thenReturn(session);
Session session = createSession(SESSION_ID, USER_NAME, NOW);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(sessionInfo.getLastRequest()).isEqualTo(NOW);
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isFalse();
}
@Test
public void sessionInformationForExpiredSession() {
ExpiringSession session = createSession(SESSION_ID, USER_NAME, NOW.getTime());
Session session = createSession(SESSION_ID, USER_NAME, NOW);
session.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
Boolean.TRUE);
when(this.sessionRepository.getSession(SESSION_ID)).thenReturn(session);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
assertThat(sessionInfo.getLastRequest()).isEqualTo(NOW);
assertThat(sessionInfo.getLastRequest().toInstant()).isEqualTo(NOW);
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
assertThat(sessionInfo.isExpired()).isTrue();
}
@@ -126,8 +125,8 @@ public class SpringSessionBackedSessionRegistryTest {
@Test
public void expireNow() {
ExpiringSession session = createSession(SESSION_ID, USER_NAME, NOW.getTime());
when(this.sessionRepository.getSession(SESSION_ID)).thenReturn(session);
Session session = createSession(SESSION_ID, USER_NAME, NOW);
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
SessionInformation sessionInfo = this.sessionRegistry
.getSessionInformation(SESSION_ID);
@@ -136,16 +135,15 @@ public class SpringSessionBackedSessionRegistryTest {
sessionInfo.expireNow();
assertThat(sessionInfo.isExpired()).isTrue();
ArgumentCaptor<ExpiringSession> captor = ArgumentCaptor
.forClass(ExpiringSession.class);
ArgumentCaptor<Session> captor = ArgumentCaptor.forClass(Session.class);
verify(this.sessionRepository).save(captor.capture());
assertThat(captor.getValue().<Boolean>getAttribute(
SpringSessionBackedSessionInformation.EXPIRED_ATTR))
.isEqualTo(Boolean.TRUE);
}
private ExpiringSession createSession(String sessionId, String userName,
Long lastAccessed) {
private Session createSession(String sessionId, String userName,
Instant lastAccessed) {
MapSession session = new MapSession(sessionId);
session.setLastAccessedTime(lastAccessed);
Authentication authentication = mock(Authentication.class);
@@ -157,11 +155,11 @@ public class SpringSessionBackedSessionRegistryTest {
}
private void setUpSessions() {
ExpiringSession session1 = createSession(SESSION_ID, USER_NAME, NOW.getTime());
Session session1 = createSession(SESSION_ID, USER_NAME, NOW);
session1.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
Boolean.TRUE);
ExpiringSession session2 = createSession(SESSION_ID2, USER_NAME, NOW.getTime());
Map<String, ExpiringSession> sessions = new LinkedHashMap<>();
Session session2 = createSession(SESSION_ID2, USER_NAME, NOW);
Map<String, Session> sessions = new LinkedHashMap<>();
sessions.put(session1.getId(), session1);
sessions.put(session2.getId(), session2);
when(this.sessionRepository.findByIndexNameAndIndexValue(

View File

@@ -17,6 +17,7 @@
package org.springframework.session.web.http;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
@@ -49,7 +50,6 @@ import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
@@ -74,11 +74,11 @@ public class SessionRepositoryFilterTests {
@Mock
private HttpSessionStrategy strategy;
private Map<String, ExpiringSession> sessions;
private Map<String, Session> sessions;
private SessionRepository<ExpiringSession> sessionRepository;
private SessionRepository<MapSession> sessionRepository;
private SessionRepositoryFilter<ExpiringSession> filter;
private SessionRepositoryFilter<MapSession> filter;
private MockHttpServletRequest request;
@@ -126,7 +126,7 @@ public class SessionRepositoryFilterTests {
@Test
public void doFilterCreateSetsLastAccessedTime() throws Exception {
MapSession session = new MapSession();
session.setLastAccessedTime(0L);
session.setLastAccessedTime(Instant.EPOCH);
this.sessionRepository = spy(this.sessionRepository);
given(this.sessionRepository.createSession()).willReturn(session);
this.filter = new SessionRepositoryFilter<>(
@@ -422,7 +422,7 @@ public class SessionRepositoryFilterTests {
public void doFilterSetsCookieIfChanged() throws Exception {
this.sessionRepository = new MapSessionRepository() {
@Override
public ExpiringSession getSession(String id) {
public MapSession findById(String id) {
return createSession();
}
};
@@ -539,7 +539,7 @@ public class SessionRepositoryFilterTests {
// the old session was removed
final String changedSessionId = getSessionCookie().getValue();
assertThat(originalSessionId).isNotEqualTo(changedSessionId);
assertThat(this.sessionRepository.getSession(originalSessionId)).isNull();
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
nextRequest();
@@ -1051,7 +1051,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1066,7 +1066,7 @@ public class SessionRepositoryFilterTests {
wrappedResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Error");
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1080,7 +1080,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.sendRedirect("/");
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1094,7 +1094,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.flushBuffer();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1108,7 +1108,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getOutputStream().flush();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1122,7 +1122,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getOutputStream().close();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1136,7 +1136,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getWriter().flush();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1150,7 +1150,7 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getWriter().close();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.getSession(id)).isNotNull();
.findById(id)).isNotNull();
}
});
}
@@ -1187,7 +1187,7 @@ public class SessionRepositoryFilterTests {
});
HttpServletRequest request = (HttpServletRequest) this.chain.getRequest();
Session session = this.sessionRepository.getSession(request.getSession().getId());
Session session = this.sessionRepository.findById(request.getSession().getId());
verify(this.strategy).onNewSession(eq(session), any(HttpServletRequest.class),
any(HttpServletResponse.class));
}
@@ -1256,8 +1256,7 @@ public class SessionRepositoryFilterTests {
@SuppressWarnings("unchecked")
public void doFilterRequestSessionNoRequestSessionNoSessionRepositoryInteractions()
throws Exception {
SessionRepository<ExpiringSession> sessionRepository = spy(
new MapSessionRepository());
SessionRepository<MapSession> sessionRepository = spy(new MapSessionRepository());
this.filter = new SessionRepositoryFilter<>(sessionRepository);
@@ -1284,8 +1283,7 @@ public class SessionRepositoryFilterTests {
@Test
public void doFilterLazySessionCreation() throws Exception {
SessionRepository<ExpiringSession> sessionRepository = spy(
new MapSessionRepository());
SessionRepository<MapSession> sessionRepository = spy(new MapSessionRepository());
this.filter = new SessionRepositoryFilter<>(sessionRepository);
@@ -1301,10 +1299,9 @@ public class SessionRepositoryFilterTests {
@Test
public void doFilterLazySessionUpdates() throws Exception {
ExpiringSession session = this.sessionRepository.createSession();
MapSession session = this.sessionRepository.createSession();
this.sessionRepository.save(session);
SessionRepository<ExpiringSession> sessionRepository = spy(
this.sessionRepository);
SessionRepository<MapSession> sessionRepository = spy(this.sessionRepository);
setSessionCookie(session.getId());
this.filter = new SessionRepositoryFilter<>(sessionRepository);
@@ -1366,7 +1363,7 @@ public class SessionRepositoryFilterTests {
// will not find the session)
HttpSession session = wrappedRequest.getSession(false);
verify(SessionRepositoryFilterTests.this.sessionRepository, times(1))
.getSession(nonExistantSessionId);
.findById(nonExistantSessionId);
assertThat(session).isNull();
assertThat(SessionRepositoryFilterTests.this.request
.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR))
@@ -1375,7 +1372,7 @@ public class SessionRepositoryFilterTests {
// Second call should not reach the sessionRepository
session = wrappedRequest.getSession(false);
verify(SessionRepositoryFilterTests.this.sessionRepository, times(1))
.getSession(nonExistantSessionId); // still only called once
.findById(nonExistantSessionId); // still only called once
assertThat(session).isNull();
assertThat(SessionRepositoryFilterTests.this.request
.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR))

View File

@@ -0,0 +1,146 @@
/*
* 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.
* 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.server.session;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpCookie;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.session.ReactorSessionRepository;
import org.springframework.session.Session;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionIdResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringSessionWebSessionManager}.
*
* @author Rob Winch
* @since 5.0
*/
@RunWith(MockitoJUnitRunner.class)
public class SpringSessionWebSessionManagerTests<S extends Session> {
@Mock
private ReactorSessionRepository<S> sessions;
@Mock
private WebSessionIdResolver resolver;
@Mock
private S createSession;
@Mock
private S findByIdSession;
private Mono<S> createSessionMono;
private ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
private SpringSessionWebSessionManager manager;
@Before
public void setup() {
given(this.createSession.getId()).willReturn("createSession-id");
given(this.findByIdSession.getId()).willReturn("findByIdSession-id");
this.createSessionMono = Mono.just(this.createSession);
given(this.sessions.createSession()).willReturn(this.createSessionMono);
this.manager = new SpringSessionWebSessionManager(this.sessions);
this.manager.setSessionIdResolver(this.resolver);
}
@Test
public void getSessionWhenDefaultSessionIdResolverFoundSessionUsed() {
String findByIdSessionId = this.findByIdSession.getId();
this.exchange = MockServerHttpRequest.get("/")
.cookie(new HttpCookie("SESSION", findByIdSessionId)).toExchange();
this.manager = new SpringSessionWebSessionManager(this.sessions);
given(this.sessions.findById(findByIdSessionId))
.willReturn(Mono.just(this.findByIdSession));
WebSession webSession = this.manager.getSession(this.exchange).block();
assertThat(webSession.getId()).isEqualTo(findByIdSessionId);
verify(this.sessions).findById(findByIdSessionId);
}
@Test
public void getSessionWhenNewThenCreateSessionInvoked() {
WebSession webSession = this.manager.getSession(this.exchange).block();
assertThat(webSession.getId()).isEqualTo(this.createSession.getId());
verify(this.sessions).createSession();
}
@Test
public void getSessionWhenNewAndPutThenSetAttributeInvoked() {
String attrName = "attrName";
String attrValue = "attrValue";
WebSession webSession = this.manager.getSession(this.exchange).block();
webSession.getAttributes().put(attrName, attrValue);
verify(this.createSession).setAttribute(attrName, attrValue);
}
@Test
public void getSessionWhenInvalidIdThenCreateSessionInvoked() {
String invalidId = "invalid";
String createSessionId = this.createSession.getId();
given(this.sessions.findById(any())).willReturn(Mono.empty());
given(this.resolver.resolveSessionIds(this.exchange))
.willReturn(Collections.singletonList(invalidId));
WebSession webSession = this.manager.getSession(this.exchange).block();
assertThat(webSession.getId()).isEqualTo(createSessionId);
verify(this.sessions).findById(invalidId);
Mono<String> mono = Mono.just("toTest");
StepVerifier.create(mono).expectNoEvent(Duration.ZERO);
}
@Test
public void getSessionWhenValidIdThenFoundSessionUsed() {
String findByIdSessionId = this.findByIdSession.getId();
given(this.sessions.findById(findByIdSessionId))
.willReturn(Mono.just(this.findByIdSession));
given(this.resolver.resolveSessionIds(this.exchange))
.willReturn(Arrays.asList(findByIdSessionId));
WebSession webSession = this.manager.getSession(this.exchange).block();
assertThat(webSession.getId()).isEqualTo(findByIdSessionId);
verify(this.sessions).findById(findByIdSessionId);
}
}

View File

@@ -0,0 +1,266 @@
/*
* 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.
* 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.server.session;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import org.springframework.session.ReactorSessionRepository;
import org.springframework.session.Session;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringSessionWebSessionStore}.
*
* @author Rob Winch
* @since 5.0
*/
@RunWith(MockitoJUnitRunner.class)
public class SpringSessionWebSessionStoreTests<S extends Session> {
@Mock
private ReactorSessionRepository<S> sessionRepository;
@Mock
private S createSession;
@Mock
private S findByIdSession;
private SpringSessionWebSessionStore<S> webSessionStore;
@Before
public void setup() {
this.webSessionStore = new SpringSessionWebSessionStore<>(this.sessionRepository);
given(this.sessionRepository.findById(any()))
.willReturn(Mono.just(this.findByIdSession));
given(this.sessionRepository.createSession())
.willReturn(Mono.just(this.createSession));
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenNullRepositoryThenThrowsIllegalArgumentException() {
new SpringSessionWebSessionStore<S>(null);
}
@Test
public void createSessionWhenNoAttributesThenNotStarted() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
assertThat(createdWebSession.isStarted()).isFalse();
}
@Test
public void createSessionWhenAddAttributeThenStarted() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createSession().block();
assertThat(createdWebSession.isStarted()).isTrue();
}
@Test
public void createSessionWhenGetAttributesAndSizeThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.size()).isEqualTo(0);
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
assertThat(attributes.size()).isEqualTo(1);
}
@Test
public void createSessionWhenGetAttributesAndIsEmptyThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.isEmpty()).isTrue();
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
assertThat(attributes.isEmpty()).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndNotStringThenFalse() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey(1L)).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndNotFoundThenFalse() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey("a")).isFalse();
}
@Test
public void createSessionWhenGetAttributesAndContainsKeyAndFoundThenTrue() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.containsKey("a")).isTrue();
}
@Test
public void createSessionWhenGetAttributesAndPutThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.put("a", "b");
verify(this.createSession).setAttribute("a", "b");
}
@Test
public void createSessionWhenGetAttributesAndPutNullThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.put("a", null);
verify(this.createSession).setAttribute("a", null);
}
@Test
public void createSessionWhenGetAttributesAndRemoveThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.remove("a");
verify(this.createSession).removeAttribute("a");
}
@Test
public void createSessionWhenGetAttributesAndPutAllThenDelegatesToCreateSession() {
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.putAll(Collections.singletonMap("a", "b"));
verify(this.createSession).setAttribute("a", "b");
}
@Test
public void createSessionWhenGetAttributesAndClearThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
attributes.clear();
verify(this.createSession).removeAttribute("a");
}
@Test
public void createSessionWhenGetAttributesAndKeySetThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.keySet()).containsExactly("a");
}
@Test
public void createSessionWhenGetAttributesAndValuesThenDelegatesToCreateSession() {
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton("a"));
given(this.createSession.getAttribute("a")).willReturn("b");
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
assertThat(attributes.values()).containsExactly("b");
}
@Test
public void createSessionWhenGetAttributesAndEntrySetThenDelegatesToCreateSession() {
String attrName = "attrName";
given(this.createSession.getAttributeNames())
.willReturn(Collections.singleton(attrName));
String attrValue = "attrValue";
given(this.createSession.getAttribute(attrName)).willReturn(attrValue);
WebSession createdWebSession = this.webSessionStore.createSession().block();
Map<String, Object> attributes = createdWebSession.getAttributes();
Set<Map.Entry<String, Object>> entries = attributes.entrySet();
assertThat(entries)
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
}
@Test
public void storeSessionWhenInvokedThenSessionSaved() {
given(this.sessionRepository.save(this.createSession)).willReturn(Mono.empty());
WebSession createdSession = this.webSessionStore.createSession().block();
this.webSessionStore.storeSession(createdSession).block();
verify(this.sessionRepository).save(this.createSession);
}
@Test
public void retrieveSessionThenStarted() {
String id = "id";
WebSession retrievedWebSession = this.webSessionStore.retrieveSession(id).block();
assertThat(retrievedWebSession.isStarted()).isTrue();
}
@Test
public void removeSessionWhenInvokedThenSessionSaved() {
String sessionId = "session-id";
given(this.sessionRepository.delete(sessionId)).willReturn(Mono.empty());
this.webSessionStore.removeSession(sessionId).block();
verify(this.sessionRepository).delete(sessionId);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.session.web.socket.server;
import java.time.Instant;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
@@ -38,13 +39,13 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -53,17 +54,17 @@ import static org.mockito.Mockito.verifyZeroInteractions;
@RunWith(MockitoJUnitRunner.class)
public class SessionRepositoryMessageInterceptorTests {
@Mock
SessionRepository<ExpiringSession> sessionRepository;
SessionRepository<Session> sessionRepository;
@Mock
MessageChannel channel;
@Mock
ExpiringSession session;
Session session;
Message<?> createMessage;
SimpMessageHeaderAccessor headers;
SessionRepositoryMessageInterceptor<ExpiringSession> interceptor;
SessionRepositoryMessageInterceptor<Session> interceptor;
@Before
public void setup() {
@@ -75,7 +76,7 @@ public class SessionRepositoryMessageInterceptorTests {
setMessageType(SimpMessageType.MESSAGE);
String sessionId = "http-session";
setSessionId(sessionId);
given(this.sessionRepository.getSession(sessionId)).willReturn(this.session);
given(this.sessionRepository.findById(sessionId)).willReturn(this.session);
}
@Test(expected = IllegalArgumentException.class)
@@ -146,7 +147,7 @@ public class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.sessionRepository).getSession(anyString());
verify(this.sessionRepository).findById(anyString());
verify(this.sessionRepository).save(this.session);
}
@@ -157,7 +158,7 @@ public class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(longThat(isAlmostNow()));
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@@ -168,7 +169,7 @@ public class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(longThat(isAlmostNow()));
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@@ -179,19 +180,19 @@ public class SessionRepositoryMessageInterceptorTests {
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(longThat(isAlmostNow()));
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@Test
public void preSendUnsubscribeUpdatesLastUpdateTime() {
setMessageType(SimpMessageType.UNSUBSCRIBE);
this.session.setLastAccessedTime(0L);
this.session.setLastAccessedTime(Instant.EPOCH);
assertThat(this.interceptor.preSend(createMessage(), this.channel))
.isSameAs(this.createMessage);
verify(this.session).setLastAccessedTime(longThat(isAlmostNow()));
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
verify(this.sessionRepository).save(this.session);
}
@@ -202,7 +203,7 @@ public class SessionRepositoryMessageInterceptorTests {
this.interceptor.preSend(createMessage(), this.channel);
verify(this.sessionRepository, times(0)).save(any(ExpiringSession.class));
verify(this.sessionRepository, times(0)).save(any(Session.class));
}
@Test
@@ -285,11 +286,11 @@ public class SessionRepositoryMessageInterceptorTests {
return new AlmostNowMatcher();
}
static class AlmostNowMatcher implements ArgumentMatcher<Long> {
static class AlmostNowMatcher implements ArgumentMatcher<Instant> {
public boolean matches(Long argument) {
public boolean matches(Instant argument) {
long now = System.currentTimeMillis();
long delta = now - argument;
long delta = now - argument.toEpochMilli();
return delta >= 0 && delta < TimeUnit.SECONDS.toMillis(3);
}

View File

@@ -1,13 +1,18 @@
apply plugin: 'io.spring.convention.spring-pom'
apply plugin: 'io.spring.convention.spring-module'
description = "Aggregator for Spring Session and Spring Data Redis"
description = "Spring Session Redis implementation"
dependencies {
compile project(':spring-session')
compile project(':spring-session-core')
compile ("org.springframework.data:spring-data-redis") {
exclude group: "org.slf4j", module: 'slf4j-api'
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
}
compile "redis.clients:jedis"
compile "org.apache.commons:commons-pool2"
testCompile "javax.servlet:javax.servlet-api"
testCompile "org.springframework:spring-web"
testCompile "org.springframework.security:spring-security-core"
integrationTestCompile "redis.clients:jedis"
integrationTestCompile "org.apache.commons:commons-pool2"
}

Some files were not shown because too many files have changed in this diff Show More