Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a739b0794c | ||
|
|
384633f8b4 | ||
|
|
30f17f96f5 | ||
|
|
ac9077b9d6 | ||
|
|
c4c7d8e233 | ||
|
|
1ce7640fc5 | ||
|
|
a50a2fe3c9 | ||
|
|
74a21dd876 | ||
|
|
c800c7af40 | ||
|
|
0033adf74e | ||
|
|
0f63b0c4c8 | ||
|
|
b1d68c0731 | ||
|
|
7ec5add1bd | ||
|
|
05e103d9c5 | ||
|
|
8190072d3f | ||
|
|
ce16374c15 | ||
|
|
e41ebd8a77 | ||
|
|
8550aeca5c | ||
|
|
5cb8a6b79a | ||
|
|
5b48e7e8e7 | ||
|
|
9e2b729d62 | ||
|
|
524ee0d9bc | ||
|
|
26be3218fb | ||
|
|
8d4fd80add | ||
|
|
6969ea0049 | ||
|
|
ce938fd2fe | ||
|
|
98d7448b40 | ||
|
|
4bb2bd6fda | ||
|
|
0e5dd1863f | ||
|
|
548b58ee55 | ||
|
|
bb28af9934 | ||
|
|
dee8402473 | ||
|
|
7bd0b45f29 | ||
|
|
b42b01af9b | ||
|
|
6744fee3cb | ||
|
|
6811f25565 | ||
|
|
47817c46e1 | ||
|
|
4f7c6406ad | ||
|
|
34cc1d1171 | ||
|
|
9e7d9912e5 | ||
|
|
d2960b570f | ||
|
|
8bd4374909 | ||
|
|
15f29f8adf | ||
|
|
74d53e8bfc | ||
|
|
77deb63373 | ||
|
|
69285f2a9a | ||
|
|
c93513f18f | ||
|
|
27044c8766 | ||
|
|
b198844671 | ||
|
|
0f27bbaff7 | ||
|
|
62ad3e1bab | ||
|
|
7eed8427a5 | ||
|
|
4cbb253c11 | ||
|
|
3fe03c60f3 | ||
|
|
d95652dcb3 | ||
|
|
cfc1a1e7ce | ||
|
|
e17d0cc1d9 | ||
|
|
0a0766e4a8 | ||
|
|
4108c77797 | ||
|
|
c015a69a4a | ||
|
|
293cf3f730 | ||
|
|
6f79e87c8f | ||
|
|
d74c5b1445 | ||
|
|
6075089691 | ||
|
|
e7a0924904 | ||
|
|
319f0a97ad | ||
|
|
95de199aa4 | ||
|
|
db589b7c29 | ||
|
|
2aae51b1a1 | ||
|
|
e721efeb85 | ||
|
|
0111c6e686 | ||
|
|
07058c0cdf | ||
|
|
5f5168814d | ||
|
|
55502f336d | ||
|
|
0e83e3f1e0 | ||
|
|
34876397a0 | ||
|
|
faee8f1bdb | ||
|
|
859784fe9e | ||
|
|
4dd2db32d2 | ||
|
|
ae86831821 | ||
|
|
b722b12327 | ||
|
|
29ff2e47fb | ||
|
|
dc9da1d5bf | ||
|
|
5a52df37ba | ||
|
|
6d161575d5 | ||
|
|
1cd8849eb9 | ||
|
|
cb3894614a | ||
|
|
82e71d834b | ||
|
|
81a9e71a5b | ||
|
|
298f0d59a0 | ||
|
|
c354284616 | ||
|
|
4086044c2f | ||
|
|
e663401ecb | ||
|
|
60151c9e7d | ||
|
|
18052460c6 | ||
|
|
5092e86306 | ||
|
|
6de6df6dab | ||
|
|
301e65c2b9 | ||
|
|
090a10fb10 | ||
|
|
235801487e | ||
|
|
e6e02de210 | ||
|
|
b3b46fd8eb | ||
|
|
e46610f53a | ||
|
|
e8c6b8db7b | ||
|
|
486d00e5da | ||
|
|
0ab781e537 | ||
|
|
849b353cec | ||
|
|
b262c9a3fd | ||
|
|
5d9e7caff0 | ||
|
|
dd348bc7b8 | ||
|
|
9372986f84 | ||
|
|
657c6a63e1 | ||
|
|
a9c2336482 | ||
|
|
068ed8d355 | ||
|
|
2b6489c2bd | ||
|
|
c0c672b9f8 | ||
|
|
46d1205ff9 | ||
|
|
cc85e927cd | ||
|
|
0819988a15 | ||
|
|
0f3ea33b50 | ||
|
|
0205c318d1 | ||
|
|
13bc1a5d24 | ||
|
|
8d2ec1ea44 | ||
|
|
729ce13390 | ||
|
|
b54fb41952 | ||
|
|
cf911322c2 | ||
|
|
6bce5ddf7f | ||
|
|
7384504871 | ||
|
|
c21fff1a00 | ||
|
|
d602880a58 | ||
|
|
2a2c430793 | ||
|
|
6080611d1d | ||
|
|
38adaeca94 | ||
|
|
6a791651e0 | ||
|
|
dfd6a0bc1b | ||
|
|
805820eeea | ||
|
|
68f867b60b | ||
|
|
1044621caf | ||
|
|
13f5cb4bac | ||
|
|
5c05970b86 | ||
|
|
0cd0bfb32f | ||
|
|
b219806d8e | ||
|
|
0f2a331ea3 | ||
|
|
ef8f667e35 | ||
|
|
4599e75c3a | ||
|
|
8a971b9ce1 | ||
|
|
56e9dcfe20 | ||
|
|
59e2cdb74f | ||
|
|
847433562e | ||
|
|
55a6967331 | ||
|
|
2c8ce67ffc | ||
|
|
076ed5cd71 | ||
|
|
f1ea71e55e | ||
|
|
5acb307a54 | ||
|
|
f921c4f527 | ||
|
|
12dc76ec36 | ||
|
|
7be3d30981 | ||
|
|
9c8fe23789 | ||
|
|
3114ef51ec | ||
|
|
9e7736bf7f | ||
|
|
6c5e335568 | ||
|
|
1deedad3b9 | ||
|
|
e4a8a6aa5c | ||
|
|
49375a28fa | ||
|
|
5375f51bca | ||
|
|
29af9d3a4d | ||
|
|
997ff56c63 | ||
|
|
06d8031211 | ||
|
|
904369ac29 | ||
|
|
266854a0be | ||
|
|
8f02c83e06 | ||
|
|
570a7686b1 | ||
|
|
fed318abc7 | ||
|
|
a824edd1c3 | ||
|
|
aa4f783b45 | ||
|
|
11fb68444f | ||
|
|
00026a30f4 | ||
|
|
c007437bd3 | ||
|
|
dda13b5619 | ||
|
|
366f13bd25 | ||
|
|
3535137c47 | ||
|
|
a9bca9088f | ||
|
|
31de86ecef | ||
|
|
d123960f89 | ||
|
|
16d2923efd | ||
|
|
24015d0854 | ||
|
|
d8f160c178 | ||
|
|
0318f6e2c1 | ||
|
|
43dd571345 | ||
|
|
e7fb9fce47 | ||
|
|
f13eb8d73e | ||
|
|
1a07ba5114 | ||
|
|
7125aac567 |
7
.github/ISSUE_TEMPLATE.md
vendored
7
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +0,0 @@
|
|||||||
<!--
|
|
||||||
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thanks for raising a Spring Session issue. Please provide a brief description of your problem along with the version of Spring Session that you are using. If possible, please also consider putting together a sample application that reproduces the issue.
|
|
||||||
-->
|
|
||||||
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: 'type: bug, status: waiting-for-triage'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior.
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Sample**
|
||||||
|
|
||||||
|
A link to a GitHub repository with a [minimal, reproducible sample](https://stackoverflow.com/help/minimal-reproducible-example).
|
||||||
|
|
||||||
|
Reports that include a sample will take priority over reports that do not.
|
||||||
|
At times, we may require a sample, so it is good to try and include a sample up front.
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Community Support
|
||||||
|
url: https://stackoverflow.com/questions/tagged/spring-security
|
||||||
|
about: Please ask and answer questions on StackOverflow with the tag spring-session
|
||||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: 'status: waiting-for-triage, type: enhancement'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Expected Behavior**
|
||||||
|
|
||||||
|
<!--- Tell us how it should work -->
|
||||||
|
|
||||||
|
**Current Behavior**
|
||||||
|
|
||||||
|
<!--- Explain the difference from current behavior -->
|
||||||
|
|
||||||
|
**Context**
|
||||||
|
|
||||||
|
<!---
|
||||||
|
How has this issue affected you?
|
||||||
|
What are you trying to accomplish?
|
||||||
|
What other alternatives have you considered?
|
||||||
|
Are you aware of any workarounds?
|
||||||
|
-->
|
||||||
101
.github/workflows/continuous-integration-workflow.yml
vendored
Normal file
101
.github/workflows/continuous-integration-workflow.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
name: 2.5.x CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 2.5.x
|
||||||
|
schedule:
|
||||||
|
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||||
|
workflow_dispatch: # Manual trigger
|
||||||
|
|
||||||
|
env:
|
||||||
|
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||||
|
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||||
|
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||||
|
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||||
|
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
jdk: [8, 11]
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK ${{ matrix.jdk }}
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: ${{ matrix.jdk }}
|
||||||
|
- name: Setup gradle user name
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.gradle
|
||||||
|
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||||
|
- name: Cache Gradle packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: |
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||||
|
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||||
|
./gradlew clean build -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --no-daemon --stacktrace
|
||||||
|
artifacts:
|
||||||
|
name: Deploy Artifacts
|
||||||
|
needs: [build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8'
|
||||||
|
- name: Setup gradle user name
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.gradle
|
||||||
|
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||||
|
- name: Deploy artifacts
|
||||||
|
run: |
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||||
|
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||||
|
export VERSION_HEADER=$'Version: GnuPG v2\n\n'
|
||||||
|
export ORG_GRADLE_PROJECT_signingKey=${GPG_PRIVATE_KEY#"$VERSION_HEADER"}
|
||||||
|
export ORG_GRADLE_PROJECT_signingPassword="$GPG_PASSPHRASE"
|
||||||
|
./gradlew deployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
|
||||||
|
./gradlew finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel
|
||||||
|
env:
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY_NO_HEADER }}
|
||||||
|
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_TOKEN_USERNAME }}
|
||||||
|
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }}
|
||||||
|
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||||
|
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||||
|
docs:
|
||||||
|
name: Deploy Docs
|
||||||
|
needs: [build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8'
|
||||||
|
- name: Setup gradle user name
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.gradle
|
||||||
|
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
|
||||||
|
- name: Deploy Docs
|
||||||
|
run: |
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||||
|
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||||
|
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||||
|
./gradlew deployDocs --no-daemon -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace
|
||||||
|
env:
|
||||||
|
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
|
||||||
|
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
|
||||||
|
DOCS_HOST: ${{ secrets.DOCS_HOST }}
|
||||||
28
.github/workflows/pr-build-workflow.yml
vendored
Normal file
28
.github/workflows/pr-build-workflow.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: 2.5.x PR Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 2.5.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
jdk: [8, 11]
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK ${{ matrix.jdk }}
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: ${{ matrix.jdk }}
|
||||||
|
- name: Cache Gradle packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew clean build --no-daemon --stacktrace
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ out
|
|||||||
.checkstyle
|
.checkstyle
|
||||||
!etc/eclipse/.checkstyle
|
!etc/eclipse/.checkstyle
|
||||||
!**/src/**/build
|
!**/src/**/build
|
||||||
|
.DS_Store
|
||||||
|
|||||||
16
.travis.yml
16
.travis.yml
@@ -1,16 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
language: java
|
|
||||||
jdk:
|
|
||||||
- openjdk8
|
|
||||||
- openjdk11
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
before_cache:
|
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
|
||||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.gradle/caches/
|
|
||||||
- $HOME/.gradle/wrapper/
|
|
||||||
install: true
|
|
||||||
script: ./gradlew clean check --no-daemon --stacktrace
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
= Contributor Code of Conduct
|
|
||||||
|
|
||||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
|
||||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
|
||||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
|
||||||
patches, and other activities.
|
|
||||||
|
|
||||||
We are committed to making participation in this project a harassment-free experience for
|
|
||||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
|
||||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
|
||||||
religion, or nationality.
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery
|
|
||||||
* Personal attacks
|
|
||||||
* Trolling or insulting/derogatory comments
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing other's private information, such as physical or electronic addresses,
|
|
||||||
without explicit permission
|
|
||||||
* Other unethical or unprofessional conduct
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
|
||||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
|
||||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
|
||||||
that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
|
||||||
consistently applying these principles to every aspect of managing this project. Project
|
|
||||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
|
||||||
from the project team.
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
|
||||||
individual is representing the project or its community.
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
|
||||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
|
||||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
|
||||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
|
||||||
with regard to the reporter of an incident.
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the
|
|
||||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
|
||||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
|
||||||
@@ -3,9 +3,16 @@
|
|||||||
Spring Session is released under the Apache 2.0 license. If you would like to contribute
|
Spring Session is released under the Apache 2.0 license. If you would like to contribute
|
||||||
something, or simply want to hack on the code this document should help you get started.
|
something, or simply want to hack on the code this document should help you get started.
|
||||||
|
|
||||||
|
|
||||||
== Code of Conduct
|
== Code of Conduct
|
||||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct].
|
||||||
|
|
||||||
|
|
||||||
|
== Reporting Security Vulnerabilities
|
||||||
|
|
||||||
|
Please see our https://github.com/spring-projects/spring-session/security/policy[Security policy].
|
||||||
|
|
||||||
|
|
||||||
== Using GitHub issues
|
== Using GitHub issues
|
||||||
|
|
||||||
@@ -19,7 +26,6 @@ information as possible. Ideally, that would include a small sample project that
|
|||||||
reproduces the problem.
|
reproduces the problem.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
== Sign the Contributor License Agreement
|
== Sign the Contributor License Agreement
|
||||||
If you have not previously done so, please fill out and
|
If you have not previously done so, please fill out and
|
||||||
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||||
|
|||||||
181
Jenkinsfile
vendored
181
Jenkinsfile
vendored
@@ -1,181 +0,0 @@
|
|||||||
properties([
|
|
||||||
buildDiscarder(logRotator(numToKeepStr: '10')),
|
|
||||||
pipelineTriggers([
|
|
||||||
cron('@daily')
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
|
|
||||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
|
||||||
currentBuild.result = SUCCESS
|
|
||||||
|
|
||||||
try {
|
|
||||||
parallel check: {
|
|
||||||
stage('Check') {
|
|
||||||
timeout(time: 45, unit: 'MINUTES') {
|
|
||||||
node('linux') {
|
|
||||||
label 'spring-session'
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
|
||||||
sh './gradlew clean check --no-daemon --stacktrace'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: check'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
junit '**/build/test-results/*/*.xml'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jdk9: {
|
|
||||||
stage('JDK 9') {
|
|
||||||
timeout(time: 45, unit: 'MINUTES') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk9'}"]) {
|
|
||||||
sh './gradlew clean test --no-daemon --stacktrace'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: jdk9'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jdk10: {
|
|
||||||
stage('JDK 10') {
|
|
||||||
timeout(time: 45, unit: 'MINUTES') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
|
|
||||||
sh './gradlew clean test --no-daemon --stacktrace'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: jdk10'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jdk11: {
|
|
||||||
stage('JDK 11') {
|
|
||||||
timeout(time: 45, unit: 'MINUTES') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
|
|
||||||
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: jdk11'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
jdk12: {
|
|
||||||
stage('JDK 12') {
|
|
||||||
timeout(time: 45, unit: 'MINUTES') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
try {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'openjdk12'}"]) {
|
|
||||||
sh './gradlew clean test integrationTest --no-daemon --stacktrace'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: jdk12'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentBuild.result == 'SUCCESS') {
|
|
||||||
parallel artifacts: {
|
|
||||||
stage('Deploy Artifacts') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
|
||||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
|
||||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
|
||||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
|
||||||
sh './gradlew deployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
|
||||||
sh './gradlew finalizeDeployArtifacts --no-daemon --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: artifacts'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
docs: {
|
|
||||||
stage('Deploy Docs') {
|
|
||||||
node('linux') {
|
|
||||||
checkout scm
|
|
||||||
sh "git clean -dfx"
|
|
||||||
try {
|
|
||||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
|
||||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
|
||||||
sh './gradlew deployDocs --no-daemon --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
currentBuild.result = 'FAILED: docs'
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
def buildStatus = currentBuild.result
|
|
||||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
|
||||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
|
||||||
|
|
||||||
if (buildNotSuccess || lastBuildNotSuccess) {
|
|
||||||
stage('Notify') {
|
|
||||||
node {
|
|
||||||
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
|
|
||||||
|
|
||||||
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
|
|
||||||
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
|
|
||||||
|
|
||||||
emailext(
|
|
||||||
subject: subject,
|
|
||||||
body: details,
|
|
||||||
recipientProviders: RECIPIENTS,
|
|
||||||
to: "$SPRING_SESSION_TEAM_EMAILS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
README.adoc
58
README.adoc
@@ -1,6 +1,8 @@
|
|||||||
= Spring Session
|
= Spring Session
|
||||||
|
|
||||||
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
|
image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
|
||||||
|
|
||||||
|
image:https://github.com/spring-projects/spring-session/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-session/actions?query=workflow%3ACI"]
|
||||||
|
|
||||||
Spring Session provides an API and implementations for managing a user's session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution.
|
Spring Session provides an API and implementations for managing a user's session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution.
|
||||||
It also provides transparent integration with:
|
It also provides transparent integration with:
|
||||||
@@ -11,21 +13,61 @@ It also provides transparent integration with:
|
|||||||
|
|
||||||
== Modules
|
== Modules
|
||||||
|
|
||||||
Spring Session consists of the following modules:
|
This Spring Session repository consists of the following modules:
|
||||||
|
|
||||||
* Spring Session Core - provides core Spring Session functionalities and APIs
|
* Spring Session Core - provides core Spring Session functionalities and APIs
|
||||||
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
|
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
|
||||||
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
|
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
|
||||||
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
|
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
|
||||||
|
|
||||||
|
Additional Spring Session modules can be found in the https://github.com/spring-projects/spring-session-data-mongodb[spring-session-data-mongodb] repository
|
||||||
|
and https://github.com/spring-projects/spring-session-data-geode[spring-session-data-geode] repository.
|
||||||
|
|
||||||
|
== Getting Started
|
||||||
|
|
||||||
|
We recommend you visit the https://docs.spring.io/spring-session/docs/current/reference/html5/#samples[Spring Session Reference] and look through the "Samples and Guides" section to see which one best suits your needs.
|
||||||
|
|
||||||
|
== Samples
|
||||||
|
|
||||||
|
Spring Session samples are available in the https://github.com/spring-projects/spring-session/tree/main/spring-session-samples[spring-session-samples] directory.
|
||||||
|
|
||||||
|
|
||||||
|
== Contributing
|
||||||
|
|
||||||
|
Please see our https://github.com/spring-projects/spring-session/blob/main/CONTRIBUTING.adoc[Contributing guidelines]
|
||||||
|
for information on how to report issues, enhancements or security vulnerabilities.
|
||||||
|
|
||||||
|
== Building from Source
|
||||||
|
|
||||||
|
Spring Session uses a https://gradle.org[Gradle]-based build system.
|
||||||
|
In the instructions below, `./gradlew` is invoked from the root of the source tree and serves as
|
||||||
|
a cross-platform, self-contained bootstrap mechanism for the build.
|
||||||
|
|
||||||
|
Check out sources
|
||||||
|
----
|
||||||
|
git clone git@github.com:spring-projects/spring-session.git
|
||||||
|
----
|
||||||
|
|
||||||
|
Install all spring-\* jars into your local Maven cache
|
||||||
|
----
|
||||||
|
./gradlew install
|
||||||
|
----
|
||||||
|
|
||||||
|
Compile and test; build all jars, distribution zips, and docs
|
||||||
|
----
|
||||||
|
./gradlew build
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
== Documentation
|
||||||
|
|
||||||
|
You can find the documentation, samples, and guides for using Spring Session on the https://projects.spring.io/spring-session/[Spring Session project site].
|
||||||
|
|
||||||
|
For more in depth information, visit the https://docs.spring.io/spring-session/docs/current/reference/html5/[Spring Session Reference].
|
||||||
|
|
||||||
== Code of Conduct
|
== Code of Conduct
|
||||||
|
|
||||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct].
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
|
||||||
|
|
||||||
== Spring Session Project Site
|
|
||||||
|
|
||||||
You can find the documentation, issue management, support, samples, and guides for using Spring Session at https://projects.spring.io/spring-session/
|
|
||||||
|
|
||||||
== License
|
== License
|
||||||
|
|
||||||
|
|||||||
13
build.gradle
13
build.gradle
@@ -4,16 +4,25 @@ buildscript {
|
|||||||
snapshotBuild = version.endsWith('SNAPSHOT')
|
snapshotBuild = version.endsWith('SNAPSHOT')
|
||||||
milestoneBuild = !(releaseBuild || snapshotBuild)
|
milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||||
|
|
||||||
springBootVersion = '2.2.4.RELEASE'
|
springBootVersion = '2.4.7'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
maven { url 'https://repo.spring.io/plugins-release/' }
|
maven { url 'https://repo.spring.io/plugins-release/' }
|
||||||
|
maven {
|
||||||
|
url = 'https://repo.spring.io/plugins-snapshot'
|
||||||
|
if (project.hasProperty('artifactoryUsername')) {
|
||||||
|
credentials {
|
||||||
|
username "$artifactoryUsername"
|
||||||
|
password "$artifactoryPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.27.RELEASE'
|
classpath 'io.spring.gradle:spring-build-conventions:0.0.37'
|
||||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
version=2.3.0.M1
|
version=2.5.1
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
imports {
|
imports {
|
||||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-SR4'
|
mavenBom 'io.projectreactor:reactor-bom:2020.0.7'
|
||||||
mavenBom 'org.junit:junit-bom:5.5.2'
|
mavenBom 'org.junit:junit-bom:5.7.2'
|
||||||
mavenBom 'org.springframework:spring-framework-bom:5.2.3.RELEASE'
|
mavenBom 'org.springframework:spring-framework-bom:5.3.8'
|
||||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Neumann-M2'
|
mavenBom 'org.springframework.data:spring-data-bom:2021.0.1'
|
||||||
mavenBom 'org.springframework.security:spring-security-bom:5.3.0.M1'
|
mavenBom 'org.springframework.security:spring-security-bom:5.5.1'
|
||||||
mavenBom 'org.testcontainers:testcontainers-bom:1.12.2'
|
mavenBom 'org.testcontainers:testcontainers-bom:1.15.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
dependencySet(group: 'com.hazelcast', version: '3.12.5') {
|
dependencySet(group: 'com.hazelcast', version: '3.12.12') {
|
||||||
entry 'hazelcast'
|
entry 'hazelcast'
|
||||||
entry 'hazelcast-client'
|
entry 'hazelcast-client'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependency 'com.h2database:h2:1.4.199'
|
dependency 'org.aspectj:aspectjweaver:1.9.6'
|
||||||
dependency 'com.ibm.db2:jcc:11.5.0.0'
|
dependency 'com.h2database:h2:1.4.200'
|
||||||
|
dependency 'com.ibm.db2:jcc:11.5.5.0'
|
||||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.4.1.jre8'
|
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.4.1.jre8'
|
||||||
dependency 'com.oracle.ojdbc:ojdbc8:19.3.0.0'
|
dependency 'com.oracle.database.jdbc:ojdbc8:19.10.0.0'
|
||||||
dependency 'com.zaxxer:HikariCP:3.4.1'
|
dependency 'com.zaxxer:HikariCP:3.4.5'
|
||||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||||
dependency 'io.lettuce:lettuce-core:5.2.0.RELEASE'
|
dependency 'io.lettuce:lettuce-core:6.1.2.RELEASE'
|
||||||
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
||||||
dependency 'javax.servlet:javax.servlet-api:4.0.1'
|
dependency 'javax.servlet:javax.servlet-api:4.0.1'
|
||||||
dependency 'junit:junit:4.12'
|
dependency 'junit:junit:4.13.2'
|
||||||
dependency 'mysql:mysql-connector-java:8.0.17'
|
dependency 'mysql:mysql-connector-java:8.0.25'
|
||||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||||
dependency 'org.assertj:assertj-core:3.13.2'
|
dependency 'org.assertj:assertj-core:3.19.0'
|
||||||
dependency 'org.hsqldb:hsqldb:2.5.0'
|
dependency 'org.hsqldb:hsqldb:2.5.2'
|
||||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.4'
|
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.3'
|
||||||
dependency 'org.mockito:mockito-core:3.0.0'
|
dependency 'org.mockito:mockito-core:3.10.0'
|
||||||
dependency 'org.postgresql:postgresql:42.2.8'
|
dependency 'org.postgresql:postgresql:42.2.22'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
31
gradlew
vendored
31
gradlew
vendored
@@ -82,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -129,6 +130,7 @@ fi
|
|||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -175,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# 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"
|
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
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -51,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url 'https://repo.spring.io/plugins-release' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "com.gradle.enterprise" version "3.5.1"
|
||||||
|
id "io.spring.ge.conventions" version "0.0.7"
|
||||||
|
}
|
||||||
|
|
||||||
rootProject.name = 'spring-session-build'
|
rootProject.name = 'spring-session-build'
|
||||||
|
|
||||||
include 'spring-session-core'
|
include 'spring-session-core'
|
||||||
@@ -5,6 +17,8 @@ include 'spring-session-data-redis'
|
|||||||
include 'spring-session-docs'
|
include 'spring-session-docs'
|
||||||
include 'spring-session-hazelcast'
|
include 'spring-session-hazelcast'
|
||||||
include 'spring-session-jdbc'
|
include 'spring-session-jdbc'
|
||||||
|
include 'hazelcast4'
|
||||||
|
project(':hazelcast4').projectDir = file('spring-session-hazelcast/hazelcast4')
|
||||||
|
|
||||||
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
||||||
include dir.name
|
include dir.name
|
||||||
|
|||||||
@@ -25,5 +25,6 @@ dependencies {
|
|||||||
testCompile "org.springframework.security:spring-security-core"
|
testCompile "org.springframework.security:spring-security-core"
|
||||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||||
testCompile "org.junit.jupiter:junit-jupiter-params"
|
testCompile "org.junit.jupiter:junit-jupiter-params"
|
||||||
|
testCompile "org.aspectj:aspectjweaver"
|
||||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import org.springframework.session.events.SessionDestroyedEvent;
|
|||||||
*
|
*
|
||||||
* {@literal @Bean}
|
* {@literal @Bean}
|
||||||
* public MapSessionRepository sessionRepository() {
|
* public MapSessionRepository sessionRepository() {
|
||||||
* return new MapSessionRepository();
|
* return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import org.springframework.util.ObjectUtils;
|
|||||||
*
|
*
|
||||||
* {@literal @Bean}
|
* {@literal @Bean}
|
||||||
* public MapSessionRepository sessionRepository() {
|
* public MapSessionRepository sessionRepository() {
|
||||||
* return new MapSessionRepository();
|
* return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import org.springframework.context.annotation.Import;
|
|||||||
*
|
*
|
||||||
* {@literal @Bean}
|
* {@literal @Bean}
|
||||||
* public ReactiveSessionRepository sessionRepository() {
|
* public ReactiveSessionRepository sessionRepository() {
|
||||||
* return new ReactiveMapSessionRepository();
|
* return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
|||||||
/**
|
/**
|
||||||
* Sets the maxAge property of the Cookie. The default is to delete the cookie when
|
* Sets the maxAge property of the Cookie. The default is to delete the cookie when
|
||||||
* the browser is closed.
|
* the browser is closed.
|
||||||
* @param cookieMaxAge the maxAge property of the Cookie
|
* @param cookieMaxAge the maxAge property of the Cookie (defined in seconds)
|
||||||
*/
|
*/
|
||||||
public void setCookieMaxAge(int cookieMaxAge) {
|
public void setCookieMaxAge(int cookieMaxAge) {
|
||||||
this.cookieMaxAge = cookieMaxAge;
|
this.cookieMaxAge = cookieMaxAge;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ abstract class OncePerRequestFilter implements Filter {
|
|||||||
}
|
}
|
||||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
String alreadyFilteredAttributeName = this.alreadyFilteredAttributeName;
|
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
|
||||||
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
|
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
|
||||||
|
|
||||||
if (hasAlreadyFilteredAttribute) {
|
if (hasAlreadyFilteredAttribute) {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import org.springframework.session.SessionRepository;
|
|||||||
* . The default is to look in a cookie named SESSION.</li>
|
* . The default is to look in a cookie named SESSION.</li>
|
||||||
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
|
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
|
||||||
* to the client using
|
* to the client using
|
||||||
|
* {@link HttpSessionIdResolver#setSessionId(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, String)}
|
||||||
* <li>The client is notified that the session id is no longer valid with
|
* <li>The client is notified that the session id is no longer valid with
|
||||||
* {@link HttpSessionIdResolver#expireSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
|
* {@link HttpSessionIdResolver#expireSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
|
||||||
* </li>
|
* </li>
|
||||||
@@ -309,6 +310,10 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
|||||||
if (!create) {
|
if (!create) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
|
||||||
|
&& this.response.isCommitted()) {
|
||||||
|
throw new IllegalStateException("Cannot create a session after the response has been committed");
|
||||||
|
}
|
||||||
if (SESSION_LOGGER.isDebugEnabled()) {
|
if (SESSION_LOGGER.isDebugEnabled()) {
|
||||||
SESSION_LOGGER.debug(
|
SESSION_LOGGER.debug(
|
||||||
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
|
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import static org.mockito.BDDMockito.given;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SpringSessionRememberMeServices}.
|
* Tests for {@link SpringSessionRememberMeServices}.
|
||||||
@@ -88,7 +88,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||||
this.rememberMeServices.autoLogin(request, response);
|
this.rememberMeServices.autoLogin(request, response);
|
||||||
verifyZeroInteractions(request, response);
|
verifyNoMoreInteractions(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// gh-752
|
// gh-752
|
||||||
@@ -102,7 +102,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
this.rememberMeServices.loginFail(request, response);
|
this.rememberMeServices.loginFail(request, response);
|
||||||
verify(request, times(1)).getSession(eq(false));
|
verify(request, times(1)).getSession(eq(false));
|
||||||
verify(session, times(1)).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
verify(session, times(1)).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
||||||
verifyZeroInteractions(request, response, session);
|
verifyNoMoreInteractions(request, response, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -119,7 +119,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
verify(request, times(1)).getSession();
|
verify(request, times(1)).getSession();
|
||||||
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||||
verifyZeroInteractions(request, response, session, authentication);
|
verifyNoMoreInteractions(request, response, session, authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -137,7 +137,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
verify(request, times(1)).getSession();
|
verify(request, times(1)).getSession();
|
||||||
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||||
verifyZeroInteractions(request, response, session, authentication);
|
verifyNoMoreInteractions(request, response, session, authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -153,7 +153,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
verify(request, times(1)).getSession();
|
verify(request, times(1)).getSession();
|
||||||
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||||
verifyZeroInteractions(request, response, session, authentication);
|
verifyNoMoreInteractions(request, response, session, authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -171,7 +171,7 @@ class SpringSessionRememberMeServicesTests {
|
|||||||
verify(request, times(1)).getSession();
|
verify(request, times(1)).getSession();
|
||||||
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
verify(request, times(1)).setAttribute(eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||||
verify(session, times(1)).setMaxInactiveInterval(eq(100000));
|
verify(session, times(1)).setMaxInactiveInterval(eq(100000));
|
||||||
verifyZeroInteractions(request, response, session, authentication);
|
verifyNoMoreInteractions(request, response, session, authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.web.http;
|
||||||
|
|
||||||
|
import org.aspectj.lang.annotation.AfterReturning;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
import org.springframework.mock.web.MockFilterChain;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
import org.springframework.session.web.http.OncePerRequestFilterAopTests.Config;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
|
||||||
|
@SpringJUnitConfig(classes = Config.class)
|
||||||
|
class OncePerRequestFilterAopTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doFilterOnce(@Autowired final OncePerRequestFilter filter) {
|
||||||
|
assertThatCode(() -> filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(),
|
||||||
|
new MockFilterChain())).as("`doFilter` does not throw NPE with the bean is being proxied by Spring AOP")
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
@Configuration
|
||||||
|
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||||
|
@Aspect
|
||||||
|
public static class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SessionRepository sessionRepository() {
|
||||||
|
return Mockito.mock(SessionRepository.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SessionRepositoryFilter filter() {
|
||||||
|
return new SessionRepositoryFilter(sessionRepository());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterReturning("execution(* SessionRepositoryFilter.doFilterInternal(..))")
|
||||||
|
public void doInternalFilterPointcut() {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ import org.springframework.session.events.SessionDestroyedEvent;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SessionEventHttpSessionListenerAdapter}.
|
* Tests for {@link SessionEventHttpSessionListenerAdapter}.
|
||||||
@@ -97,7 +97,7 @@ class SessionEventHttpSessionListenerAdapterTests {
|
|||||||
|
|
||||||
this.listener.onApplicationEvent(this.destroyed);
|
this.listener.onApplicationEvent(this.destroyed);
|
||||||
|
|
||||||
verifyZeroInteractions(this.destroyed, this.listener1, this.listener2);
|
verifyNoMoreInteractions(this.destroyed, this.listener1, this.listener2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
import static org.assertj.core.api.Assertions.fail;
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
@@ -72,7 +73,7 @@ import static org.mockito.Mockito.reset;
|
|||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SessionRepositoryFilter}.
|
* Tests for {@link SessionRepositoryFilter}.
|
||||||
@@ -423,6 +424,18 @@ class SessionRepositoryFilterTests {
|
|||||||
assertThat(this.response.getCookie("SESSION")).isNotNull();
|
assertThat(this.response.getCookie("SESSION")).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doFilterGetSessionNewWhenResponseCommittedThenException() {
|
||||||
|
assertThatIllegalStateException().isThrownBy(() -> doFilter(new DoInFilter() {
|
||||||
|
@Override
|
||||||
|
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse)
|
||||||
|
throws IOException {
|
||||||
|
wrappedResponse.getWriter().flush();
|
||||||
|
wrappedRequest.getSession();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void doFilterGetSessionNew() throws Exception {
|
void doFilterGetSessionNew() throws Exception {
|
||||||
doFilter(new DoInFilter() {
|
doFilter(new DoInFilter() {
|
||||||
@@ -1260,7 +1273,7 @@ class SessionRepositoryFilterTests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
verifyZeroInteractions(sessionRepository);
|
verifyNoMoreInteractions(sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1275,7 +1288,7 @@ class SessionRepositoryFilterTests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
verifyZeroInteractions(sessionRepository);
|
verifyNoMoreInteractions(sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1293,7 +1306,7 @@ class SessionRepositoryFilterTests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
verifyZeroInteractions(sessionRepository);
|
verifyNoMoreInteractions(sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1318,7 +1331,7 @@ class SessionRepositoryFilterTests {
|
|||||||
verify(sessionRepository).deleteById(eq(session.getId()));
|
verify(sessionRepository).deleteById(eq(session.getId()));
|
||||||
verify(sessionRepository).createSession();
|
verify(sessionRepository).createSession();
|
||||||
verify(sessionRepository).save(any());
|
verify(sessionRepository).save(any());
|
||||||
verifyZeroInteractions(sessionRepository);
|
verifyNoMoreInteractions(sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- order
|
// --- order
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import static org.mockito.ArgumentMatchers.argThat;
|
|||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
class SessionRepositoryMessageInterceptorTests {
|
class SessionRepositoryMessageInterceptorTests {
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -107,7 +107,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,7 +116,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -125,7 +125,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -209,7 +209,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -218,14 +218,14 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
|
|
||||||
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
assertThat(this.interceptor.preSend(createMessage(), this.channel)).isSameAs(this.createMessage);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void beforeHandshakeNotServletServerHttpRequest() {
|
void beforeHandshakeNotServletServerHttpRequest() {
|
||||||
assertThat(this.interceptor.beforeHandshake(null, null, null, null)).isTrue();
|
assertThat(this.interceptor.beforeHandshake(null, null, null, null)).isTrue();
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -233,7 +233,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
ServletServerHttpRequest request = new ServletServerHttpRequest(new MockHttpServletRequest());
|
ServletServerHttpRequest request = new ServletServerHttpRequest(new MockHttpServletRequest());
|
||||||
assertThat(this.interceptor.beforeHandshake(request, null, null, null)).isTrue();
|
assertThat(this.interceptor.beforeHandshake(request, null, null, null)).isTrue();
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -256,7 +256,7 @@ class SessionRepositoryMessageInterceptorTests {
|
|||||||
void afterHandshakeDoesNothing() {
|
void afterHandshakeDoesNothing() {
|
||||||
this.interceptor.afterHandshake(null, null, null, null);
|
this.interceptor.afterHandshake(null, null, null, null);
|
||||||
|
|
||||||
verifyZeroInteractions(this.sessionRepository);
|
verifyNoMoreInteractions(this.sessionRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSessionId(String id) {
|
private void setSessionId(String id) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractRedisITests {
|
public abstract class AbstractRedisITests {
|
||||||
|
|
||||||
private static final String DOCKER_IMAGE = "redis:5.0.6";
|
private static final String DOCKER_IMAGE = "redis:5.0.10";
|
||||||
|
|
||||||
protected static class BaseConfig {
|
protected static class BaseConfig {
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,6 +18,7 @@ package org.springframework.session.data.redis;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -36,6 +37,7 @@ import org.springframework.data.redis.core.BoundHashOperations;
|
|||||||
import org.springframework.data.redis.core.RedisOperations;
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
import org.springframework.data.redis.util.ByteUtils;
|
||||||
import org.springframework.session.DelegatingIndexResolver;
|
import org.springframework.session.DelegatingIndexResolver;
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
import org.springframework.session.FlushMode;
|
import org.springframework.session.FlushMode;
|
||||||
@@ -86,7 +88,7 @@ import org.springframework.util.Assert;
|
|||||||
* details.
|
* details.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
|
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
|
||||||
* EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
|
* EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
|
||||||
* APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
|
* APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
|
||||||
* EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
|
* EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
|
||||||
@@ -129,8 +131,8 @@ import org.springframework.util.Assert;
|
|||||||
* The {@link RedisIndexedSessionRepository.RedisSession} keeps track of the properties
|
* The {@link RedisIndexedSessionRepository.RedisSession} keeps track of the properties
|
||||||
* that have changed and only updates those. This means if an attribute is written once
|
* that have changed and only updates those. This means if an attribute is written once
|
||||||
* and read many times we only need to write that attribute once. For example, assume the
|
* and read many times we only need to write that attribute once. For example, assume the
|
||||||
* session attribute "sessionAttr2" from earlier was updated. The following would be
|
* session attribute "attrName2" from earlier was updated. The following would be executed
|
||||||
* executed upon saving:
|
* upon saving:
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
@@ -142,7 +144,7 @@ import org.springframework.util.Assert;
|
|||||||
* <p>
|
* <p>
|
||||||
* When a session is created an event is sent to Redis with the channel of
|
* When a session is created an event is sent to Redis with the channel of
|
||||||
* "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such that
|
* "spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe" such that
|
||||||
* "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the sesion id. The body of the event will be
|
* "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the session id. The body of the event will be
|
||||||
* the session that was created.
|
* the session that was created.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
@@ -272,10 +274,20 @@ public class RedisIndexedSessionRepository
|
|||||||
|
|
||||||
private String sessionCreatedChannelPrefix;
|
private String sessionCreatedChannelPrefix;
|
||||||
|
|
||||||
|
private byte[] sessionCreatedChannelPrefixBytes;
|
||||||
|
|
||||||
private String sessionDeletedChannel;
|
private String sessionDeletedChannel;
|
||||||
|
|
||||||
|
private byte[] sessionDeletedChannelBytes;
|
||||||
|
|
||||||
private String sessionExpiredChannel;
|
private String sessionExpiredChannel;
|
||||||
|
|
||||||
|
private byte[] sessionExpiredChannelBytes;
|
||||||
|
|
||||||
|
private String expiredKeyPrefix;
|
||||||
|
|
||||||
|
private byte[] expiredKeyPrefixBytes;
|
||||||
|
|
||||||
private final RedisOperations<Object, Object> sessionRedisOperations;
|
private final RedisOperations<Object, Object> sessionRedisOperations;
|
||||||
|
|
||||||
private final RedisSessionExpirationPolicy expirationPolicy;
|
private final RedisSessionExpirationPolicy expirationPolicy;
|
||||||
@@ -381,8 +393,13 @@ public class RedisIndexedSessionRepository
|
|||||||
|
|
||||||
private void configureSessionChannels() {
|
private void configureSessionChannels() {
|
||||||
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:";
|
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:";
|
||||||
|
this.sessionCreatedChannelPrefixBytes = this.sessionCreatedChannelPrefix.getBytes();
|
||||||
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
|
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
|
||||||
|
this.sessionDeletedChannelBytes = this.sessionDeletedChannel.getBytes();
|
||||||
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
|
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
|
||||||
|
this.sessionExpiredChannelBytes = this.sessionExpiredChannel.getBytes();
|
||||||
|
this.expiredKeyPrefix = this.namespace + "sessions:expires:";
|
||||||
|
this.expiredKeyPrefixBytes = this.expiredKeyPrefix.getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -501,25 +518,24 @@ public class RedisIndexedSessionRepository
|
|||||||
@Override
|
@Override
|
||||||
public void onMessage(Message message, byte[] pattern) {
|
public void onMessage(Message message, byte[] pattern) {
|
||||||
byte[] messageChannel = message.getChannel();
|
byte[] messageChannel = message.getChannel();
|
||||||
byte[] messageBody = message.getBody();
|
|
||||||
|
|
||||||
String channel = new String(messageChannel);
|
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
|
||||||
|
|
||||||
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
|
|
||||||
// TODO: is this thread safe?
|
// TODO: is this thread safe?
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
|
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
|
||||||
handleCreated(loaded, channel);
|
handleCreated(loaded, new String(messageChannel));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String body = new String(messageBody);
|
byte[] messageBody = message.getBody();
|
||||||
if (!body.startsWith(getExpiredKeyPrefix())) {
|
|
||||||
|
if (!ByteUtils.startsWith(messageBody, this.expiredKeyPrefixBytes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
|
boolean isDeleted = Arrays.equals(messageChannel, this.sessionDeletedChannelBytes);
|
||||||
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
|
if (isDeleted || Arrays.equals(messageChannel, this.sessionExpiredChannelBytes)) {
|
||||||
|
String body = new String(messageBody);
|
||||||
int beginIndex = body.lastIndexOf(":") + 1;
|
int beginIndex = body.lastIndexOf(":") + 1;
|
||||||
int endIndex = body.length();
|
int endIndex = body.length();
|
||||||
String sessionId = body.substring(beginIndex, endIndex);
|
String sessionId = body.substring(beginIndex, endIndex);
|
||||||
@@ -611,7 +627,7 @@ public class RedisIndexedSessionRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getExpiredKeyPrefix() {
|
private String getExpiredKeyPrefix() {
|
||||||
return this.namespace + "sessions:expires:";
|
return this.expiredKeyPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -50,6 +50,8 @@ final class RedisSessionExpirationPolicy {
|
|||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(RedisSessionExpirationPolicy.class);
|
private static final Log logger = LogFactory.getLog(RedisSessionExpirationPolicy.class);
|
||||||
|
|
||||||
|
private static final String SESSION_EXPIRES_PREFIX = "expires:";
|
||||||
|
|
||||||
private final RedisOperations<Object, Object> redis;
|
private final RedisOperations<Object, Object> redis;
|
||||||
|
|
||||||
private final Function<Long, String> lookupExpirationKey;
|
private final Function<Long, String> lookupExpirationKey;
|
||||||
@@ -67,11 +69,12 @@ final class RedisSessionExpirationPolicy {
|
|||||||
void onDelete(Session session) {
|
void onDelete(Session session) {
|
||||||
long toExpire = roundUpToNextMinute(expiresInMillis(session));
|
long toExpire = roundUpToNextMinute(expiresInMillis(session));
|
||||||
String expireKey = getExpirationKey(toExpire);
|
String expireKey = getExpirationKey(toExpire);
|
||||||
this.redis.boundSetOps(expireKey).remove(session.getId());
|
String entryToRemove = SESSION_EXPIRES_PREFIX + session.getId();
|
||||||
|
this.redis.boundSetOps(expireKey).remove(entryToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
|
void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
|
||||||
String keyToExpire = "expires:" + session.getId();
|
String keyToExpire = SESSION_EXPIRES_PREFIX + session.getId();
|
||||||
long toExpire = roundUpToNextMinute(expiresInMillis(session));
|
long toExpire = roundUpToNextMinute(expiresInMillis(session));
|
||||||
|
|
||||||
if (originalExpirationTimeInMilli != null) {
|
if (originalExpirationTimeInMilli != null) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -42,13 +42,13 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
|
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
|
||||||
|
|
||||||
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
|
private static final String DEFAULT_KEY_NAMESPACE = "spring:session";
|
||||||
|
|
||||||
private final RedisOperations<String, Object> sessionRedisOperations;
|
private final RedisOperations<String, Object> sessionRedisOperations;
|
||||||
|
|
||||||
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
|
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
|
||||||
|
|
||||||
private String keyNamespace = DEFAULT_KEY_NAMESPACE;
|
private String keyNamespace = DEFAULT_KEY_NAMESPACE + ":";
|
||||||
|
|
||||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||||
|
|
||||||
@@ -76,12 +76,23 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
|
|||||||
/**
|
/**
|
||||||
* Set the key namespace.
|
* Set the key namespace.
|
||||||
* @param keyNamespace the key namespace
|
* @param keyNamespace the key namespace
|
||||||
|
* @deprecated since 2.4.0 in favor of {@link #setRedisKeyNamespace(String)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setKeyNamespace(String keyNamespace) {
|
public void setKeyNamespace(String keyNamespace) {
|
||||||
Assert.hasText(keyNamespace, "keyNamespace must not be empty");
|
Assert.hasText(keyNamespace, "keyNamespace must not be empty");
|
||||||
this.keyNamespace = keyNamespace;
|
this.keyNamespace = keyNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Redis key namespace.
|
||||||
|
* @param namespace the Redis key namespace
|
||||||
|
*/
|
||||||
|
public void setRedisKeyNamespace(String namespace) {
|
||||||
|
Assert.hasText(namespace, "namespace must not be empty");
|
||||||
|
this.keyNamespace = namespace.trim() + ":";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the flush mode.
|
* Set the flush mode.
|
||||||
* @param flushMode the flush mode
|
* @param flushMode the flush mode
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ReactiveRedisSessionRepository}.
|
* Tests for {@link ReactiveRedisSessionRepository}.
|
||||||
@@ -137,8 +137,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
|
|
||||||
Map<String, Object> delta = this.delta.getAllValues().get(0);
|
Map<String, Object> delta = this.delta.getAllValues().get(0);
|
||||||
assertThat(delta.size()).isEqualTo(3);
|
assertThat(delta.size()).isEqualTo(3);
|
||||||
@@ -160,8 +160,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||||
|
|
||||||
verify(this.redisOperations).hasKey(anyString());
|
verify(this.redisOperations).hasKey(anyString());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -179,8 +179,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
|
|
||||||
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
||||||
map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, session.getLastAccessedTime().toEpochMilli()));
|
map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, session.getLastAccessedTime().toEpochMilli()));
|
||||||
@@ -202,8 +202,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
|
|
||||||
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
assertThat(this.delta.getAllValues().get(0)).isEqualTo(
|
||||||
map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName)));
|
map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), session.getAttribute(attrName)));
|
||||||
@@ -225,8 +225,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
|
|
||||||
assertThat(this.delta.getAllValues().get(0))
|
assertThat(this.delta.getAllValues().get(0))
|
||||||
.isEqualTo(map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), null));
|
.isEqualTo(map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), null));
|
||||||
@@ -252,8 +252,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
StepVerifier.create(this.repository.deleteById("test")).verifyComplete();
|
StepVerifier.create(this.repository.deleteById("test")).verifyComplete();
|
||||||
|
|
||||||
verify(this.redisOperations).delete(anyString());
|
verify(this.redisOperations).delete(anyString());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -267,8 +267,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).entries(anyString());
|
verify(this.hashOperations).entries(anyString());
|
||||||
verify(this.redisOperations).delete(anyString());
|
verify(this.redisOperations).delete(anyString());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -291,8 +291,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
|
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
|
||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).entries(anyString());
|
verify(this.hashOperations).entries(anyString());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
|
|
||||||
assertThat(session.getId()).isEqualTo(expected.getId());
|
assertThat(session.getId()).isEqualTo(expected.getId());
|
||||||
assertThat(session.getAttributeNames()).isEqualTo(expected.getAttributeNames());
|
assertThat(session.getAttributeNames()).isEqualTo(expected.getAttributeNames());
|
||||||
@@ -320,8 +320,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.redisOperations).opsForHash();
|
verify(this.redisOperations).opsForHash();
|
||||||
verify(this.hashOperations).entries(anyString());
|
verify(this.hashOperations).entries(anyString());
|
||||||
verify(this.redisOperations).delete(anyString());
|
verify(this.redisOperations).delete(anyString());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-1120
|
@Test // gh-1120
|
||||||
@@ -357,8 +357,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
assertThat(this.delta.getValue()).hasSize(1);
|
assertThat(this.delta.getValue()).hasSize(1);
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -381,8 +381,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
assertThat(this.delta.getValue()).hasSize(2);
|
assertThat(this.delta.getValue()).hasSize(2);
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -405,8 +405,8 @@ class ReactiveRedisSessionRepositoryTests {
|
|||||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||||
assertThat(this.delta.getValue()).hasSize(3);
|
assertThat(this.delta.getValue()).hasSize(3);
|
||||||
verify(this.redisOperations).expire(anyString(), any());
|
verify(this.redisOperations).expire(anyString(), any());
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.hashOperations);
|
verifyNoMoreInteractions(this.hashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> map(Object... objects) {
|
private Map<String, Object> map(Object... objects) {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
class RedisIndexedSessionRepositoryTests {
|
class RedisIndexedSessionRepositoryTests {
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
this.redisRepository.save(session);
|
this.redisRepository.save(session);
|
||||||
|
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -511,10 +511,10 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
verify(this.boundHashOperations).entries();
|
verify(this.boundHashOperations).entries();
|
||||||
verify(this.publisher).publishEvent(this.event.capture());
|
verify(this.publisher).publishEvent(this.event.capture());
|
||||||
assertThat(this.event.getValue().getSessionId()).isEqualTo(deletedId);
|
assertThat(this.event.getValue().getSessionId()).isEqualTo(deletedId);
|
||||||
verifyZeroInteractions(this.defaultSerializer);
|
verifyNoMoreInteractions(this.defaultSerializer);
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -534,10 +534,10 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
|
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
|
||||||
verify(this.boundHashOperations).entries();
|
verify(this.boundHashOperations).entries();
|
||||||
verifyZeroInteractions(this.defaultSerializer);
|
verifyNoMoreInteractions(this.defaultSerializer);
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -561,10 +561,10 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
verify(this.boundHashOperations).entries();
|
verify(this.boundHashOperations).entries();
|
||||||
verify(this.publisher).publishEvent(this.event.capture());
|
verify(this.publisher).publishEvent(this.event.capture());
|
||||||
assertThat(this.event.getValue().getSessionId()).isEqualTo(expiredId);
|
assertThat(this.event.getValue().getSessionId()).isEqualTo(expiredId);
|
||||||
verifyZeroInteractions(this.defaultSerializer);
|
verifyNoMoreInteractions(this.defaultSerializer);
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -584,17 +584,17 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
|
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
|
||||||
verify(this.boundHashOperations).entries();
|
verify(this.boundHashOperations).entries();
|
||||||
verifyZeroInteractions(this.defaultSerializer);
|
verifyNoMoreInteractions(this.defaultSerializer);
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
verifyZeroInteractions(this.redisOperations);
|
verifyNoMoreInteractions(this.redisOperations);
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeOnSaveCreate() {
|
void flushModeOnSaveCreate() {
|
||||||
this.redisRepository.createSession();
|
this.redisRepository.createSession();
|
||||||
|
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -602,7 +602,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
RedisSession session = this.redisRepository.createSession();
|
RedisSession session = this.redisRepository.createSession();
|
||||||
session.setAttribute("something", "here");
|
session.setAttribute("something", "here");
|
||||||
|
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -610,7 +610,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
RedisSession session = this.redisRepository.createSession();
|
RedisSession session = this.redisRepository.createSession();
|
||||||
session.removeAttribute("remove");
|
session.removeAttribute("remove");
|
||||||
|
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -618,7 +618,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
RedisSession session = this.redisRepository.createSession();
|
RedisSession session = this.redisRepository.createSession();
|
||||||
session.setLastAccessedTime(Instant.ofEpochMilli(1L));
|
session.setLastAccessedTime(Instant.ofEpochMilli(1L));
|
||||||
|
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -626,7 +626,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
RedisSession session = this.redisRepository.createSession();
|
RedisSession session = this.redisRepository.createSession();
|
||||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||||
|
|
||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyNoMoreInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -790,7 +790,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
assertThat(this.event.getAllValues()).isEmpty();
|
assertThat(this.event.getAllValues()).isEmpty();
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -808,7 +808,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
assertThat(this.event.getAllValues()).isEmpty();
|
assertThat(this.event.getAllValues()).isEmpty();
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -826,7 +826,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
assertThat(this.event.getAllValues()).isEmpty();
|
assertThat(this.event.getAllValues()).isEmpty();
|
||||||
verifyZeroInteractions(this.publisher);
|
verifyNoMoreInteractions(this.publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -164,4 +164,11 @@ class RedisSessionExpirationPolicyTests {
|
|||||||
verify(this.hashOperations).persist();
|
verify(this.hashOperations).persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onDeleteRemoveExpirationEntry() {
|
||||||
|
this.policy.onDelete(this.session);
|
||||||
|
|
||||||
|
verify(this.setOperations).remove("expires:" + this.session.getId());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -102,18 +102,36 @@ class RedisSessionRepositoryTests {
|
|||||||
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
|
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setRedisKeyNamespace_ValidNamespace_ShouldSetNamespace() {
|
||||||
|
this.sessionRepository.setRedisKeyNamespace("test");
|
||||||
|
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void setKeyNamespace_NullNamespace_ShouldThrowException() {
|
void setKeyNamespace_NullNamespace_ShouldThrowException() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(null))
|
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(null))
|
||||||
.withMessage("keyNamespace must not be empty");
|
.withMessage("keyNamespace must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setRedisKeyNamespace_NullNamespace_ShouldThrowException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(null))
|
||||||
|
.withMessage("namespace must not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void setKeyNamespace_EmptyNamespace_ShouldThrowException() {
|
void setKeyNamespace_EmptyNamespace_ShouldThrowException() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(" "))
|
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(" "))
|
||||||
.withMessage("keyNamespace must not be empty");
|
.withMessage("keyNamespace must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setRedisKeyNamespace_EmptyNamespace_ShouldThrowException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(" "))
|
||||||
|
.withMessage("namespace must not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void setFlushMode_ValidFlushMode_ShouldSetFlushMode() {
|
void setFlushMode_ValidFlushMode_ShouldSetFlushMode() {
|
||||||
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
|
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
@@ -185,7 +203,7 @@ class RedisSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void save_NewSessionAndCustomKeyNamespace_ShouldSaveSession() {
|
void save_NewSessionAndCustomKeyNamespace_ShouldSaveSession() {
|
||||||
this.sessionRepository.setKeyNamespace("custom:");
|
this.sessionRepository.setRedisKeyNamespace("custom");
|
||||||
RedisSession session = this.sessionRepository.createSession();
|
RedisSession session = this.sessionRepository.createSession();
|
||||||
this.sessionRepository.save(session);
|
this.sessionRepository.save(session);
|
||||||
String key = "custom:sessions:" + session.getId();
|
String key = "custom:sessions:" + session.getId();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import static org.mockito.BDDMockito.given;
|
|||||||
import static org.mockito.BDDMockito.willThrow;
|
import static org.mockito.BDDMockito.willThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
class RedisHttpSessionConfigurationMockTests {
|
class RedisHttpSessionConfigurationMockTests {
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class RedisHttpSessionConfigurationMockTests {
|
|||||||
|
|
||||||
init.afterPropertiesSet();
|
init.afterPropertiesSet();
|
||||||
|
|
||||||
verifyZeroInteractions(this.factory);
|
verifyNoMoreInteractions(this.factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -25,8 +25,23 @@ dependencies {
|
|||||||
|
|
||||||
def versions = dependencyManagement.managedVersions
|
def versions = dependencyManagement.managedVersions
|
||||||
|
|
||||||
|
asciidoctorPdf {
|
||||||
|
clearSources()
|
||||||
|
sources {
|
||||||
|
include "index.adoc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
asciidoctor {
|
asciidoctor {
|
||||||
def ghTag = snapshotBuild ? 'master' : project.version
|
clearSources()
|
||||||
|
sources {
|
||||||
|
include "index.adoc"
|
||||||
|
include "guides/*.adoc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asciidoctorj {
|
||||||
|
def ghTag = snapshotBuild ? 'main' : project.version
|
||||||
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag"
|
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag"
|
||||||
|
|
||||||
attributes 'docs-itest-dir': "$rootProject.projectDir.path/spring-session-docs/src/integration-test/java/",
|
attributes 'docs-itest-dir': "$rootProject.projectDir.path/spring-session-docs/src/integration-test/java/",
|
||||||
@@ -46,5 +61,11 @@ asciidoctor {
|
|||||||
'spring-session-version': project.version,
|
'spring-session-version': project.version,
|
||||||
'version-milestone': milestoneBuild,
|
'version-milestone': milestoneBuild,
|
||||||
'version-release': releaseBuild,
|
'version-release': releaseBuild,
|
||||||
'version-snapshot': snapshotBuild
|
'version-snapshot': snapshotBuild,
|
||||||
|
'highlightjsdir@': "js/highlight",
|
||||||
|
'docinfodir@': "."
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url "https://repo.spring.io/release" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
= Spring Session - find by username
|
= Spring Session - find by username
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to find sessions by username.
|
This guide describes how to use Spring Session to find sessions by username.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<findbyusername-sample, findbyusername application>>.
|
NOTE: You can find the completed guide in the <<findbyusername-sample, findbyusername application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
[[findbyusername-assumptions]]
|
[[findbyusername-assumptions]]
|
||||||
== Assumptions
|
== Assumptions
|
||||||
@@ -23,7 +28,7 @@ Consider the following scenario:
|
|||||||
|
|
||||||
* User goes to library and authenticates to the application.
|
* User goes to library and authenticates to the application.
|
||||||
* User goes home and realizes they forgot to log out.
|
* User goes home and realizes they forgot to log out.
|
||||||
* User can log in and terminate the session from the library using clues like the location, created time, last accessed time, and so on.
|
* User can log in and end the session from the library using clues like the location, created time, last accessed time, and so on.
|
||||||
|
|
||||||
Would it not be nice if we could let the user invalidate the session at the library from any device with which they authenticate?
|
Would it not be nice if we could let the user invalidate the session at the library from any device with which they authenticate?
|
||||||
This sample demonstrates how this is possible.
|
This sample demonstrates how this is possible.
|
||||||
@@ -140,5 +145,5 @@ You can emulate the flow we discussed in the <<About the Sample>> section by doi
|
|||||||
* Enter the following to log in:
|
* Enter the following to log in:
|
||||||
** *Username* _user_
|
** *Username* _user_
|
||||||
** *Password* _password_
|
** *Password* _password_
|
||||||
* Terminate your original session.
|
* End your original session.
|
||||||
* Refresh the original window and see that you are logged out.
|
* Refresh the original window and see that you are logged out.
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - Spring Boot
|
= Spring Session - Spring Boot
|
||||||
Rob Winch, Vedran Pavić
|
Rob Winch, Vedran Pavić
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` when you use Spring Boot.
|
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` when you use Spring Boot.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<httpsession-jdbc-boot-sample, httpsession-jdbc-boot sample application>>.
|
NOTE: You can find the completed guide in the <<httpsession-jdbc-boot-sample, httpsession-jdbc-boot sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
@@ -46,6 +52,9 @@ spring.session.store-type=jdbc # Session store type.
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
If a single Spring Session module is present on the classpath, Spring Boot uses that store implementation automatically.
|
||||||
|
If you have more than one implementation, you must choose the StoreType that you wish to use to store the sessions, as shows above.
|
||||||
|
|
||||||
Under the hood, Spring Boot applies configuration that is equivalent to manually adding the `@EnableJdbcHttpSession` annotation.
|
Under the hood, Spring Boot applies configuration that is equivalent to manually adding the `@EnableJdbcHttpSession` annotation.
|
||||||
This creates a Spring bean with the name of `springSessionRepositoryFilter`. That bean implements `Filter`.
|
This creates a Spring bean with the name of `springSessionRepositoryFilter`. That bean implements `Filter`.
|
||||||
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
The filter is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - Spring Boot
|
= Spring Session - Spring Boot
|
||||||
Rob Winch, Vedran Pavić
|
Rob Winch, Vedran Pavić
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use Spring Boot.
|
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use Spring Boot.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<boot-sample, boot sample application>>.
|
NOTE: You can find the completed guide in the <<boot-sample, boot sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must ensure your dependencies.
|
Before you use Spring Session, you must ensure your dependencies.
|
||||||
@@ -53,7 +59,7 @@ Further customization is possible by using `application.properties`, as the foll
|
|||||||
.src/main/resources/application.properties
|
.src/main/resources/application.properties
|
||||||
----
|
----
|
||||||
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
|
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
|
||||||
spring.session.redis.flush-mode=on-save # Sessions flush mode.
|
spring.session.redis.flush-mode=on_save # Sessions flush mode.
|
||||||
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
|
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
@@ -151,6 +157,6 @@ To do so, enter the following into your terminal, being sure to replace `7e8383a
|
|||||||
----
|
----
|
||||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||||
----
|
----
|
||||||
=====
|
====
|
||||||
|
|
||||||
Now you can visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
Now you can visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
= Spring Session - WebFlux with Custom Cookie
|
||||||
|
Eleftheria Stein-Kousathana
|
||||||
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
|
This guide describes how to configure Spring Session to use custom cookies in a WebFlux based application.
|
||||||
|
The guide assumes you have already set up Spring Session in your project using your chosen data store. For example, link:./boot-redis.html[HttpSession with Redis].
|
||||||
|
|
||||||
|
NOTE: You can find the completed guide in the <<webflux-custom-cookie-sample, WebFlux Custom Cookie sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
|
[[webflux-custom-cookie-spring-configuration]]
|
||||||
|
== Spring Boot Configuration
|
||||||
|
|
||||||
|
Once you have set up Spring Session, you can customize how the session cookie is written by exposing a `WebSessionIdResolver` as a Spring bean.
|
||||||
|
Spring Session uses a `CookieWebSessionIdResolver` by default.
|
||||||
|
Exposing the `WebSessionIdResolver` as a Spring bean augments the existing configuration when you use configurations like `@EnableRedisHttpSession`.
|
||||||
|
The following example shows how to customize Spring Session's cookie:
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
include::{samples-dir}spring-session-sample-boot-webflux-custom-cookie/src/main/java/sample/CookieConfig.java[tags=webflux-cookie-serializer]
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> We customize the name of the cookie to be `JSESSIONID`.
|
||||||
|
<2> We customize the path of the cookie to be `/` (rather than the default of the context root).
|
||||||
|
<3> We customize the `SameSite` cookie directive to be `Strict`.
|
||||||
|
====
|
||||||
|
|
||||||
|
[[webflux-custom-cookie-sample]]
|
||||||
|
== `webflux-custom-cookie` Sample Application
|
||||||
|
|
||||||
|
This section describes how to work with the `webflux-custom-cookie` sample application.
|
||||||
|
|
||||||
|
=== Running the `webflux-custom-cookie` Sample Application
|
||||||
|
|
||||||
|
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||||
|
|
||||||
|
====
|
||||||
|
----
|
||||||
|
$ ./gradlew :spring-session-sample-boot-webflux-custom-cookie:bootRun
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
NOTE: For the sample to work, you must https://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||||
|
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
|
||||||
|
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||||
|
|
||||||
|
You should now be able to access the application at http://localhost:8080/
|
||||||
|
|
||||||
|
=== Exploring the `webflux-custom-cookie` Sample Application
|
||||||
|
|
||||||
|
Now you can use the application. Fill out the form with the following information:
|
||||||
|
|
||||||
|
* *Attribute Name:* _username_
|
||||||
|
* *Attribute Value:* _rob_
|
||||||
|
|
||||||
|
Now click the *Set Attribute* button.
|
||||||
|
You should now see the values displayed in the table.
|
||||||
|
|
||||||
|
If you look at the cookies for the application, you can see the cookie is saved to the custom name of `JSESSIONID`.
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
= Spring Session - WebSocket
|
= Spring Session - WebSocket
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
|
:websocketdoc-test-dir: {docs-test-dir}docs/websocket/
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to ensure that WebSocket messages keep your HttpSession alive.
|
This guide describes how to use Spring Session to ensure that WebSocket messages keep your HttpSession alive.
|
||||||
|
|
||||||
@@ -12,9 +15,12 @@ Specifically,it does not work with using https://www.jcp.org/en/jsr/detail?id=35
|
|||||||
|
|
||||||
// end::disclaimer[]
|
// end::disclaimer[]
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== HttpSession Setup
|
== HttpSession Setup
|
||||||
|
|
||||||
The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the link:httpsession.html[HttpSession Guide].
|
The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the link:./boot-redis.html[HttpSession with Redis Guide].
|
||||||
|
|
||||||
Please make sure you have already integrated Spring Session with HttpSession before proceeding.
|
Please make sure you have already integrated Spring Session with HttpSession before proceeding.
|
||||||
|
|
||||||
@@ -53,14 +59,14 @@ What does `AbstractSessionWebSocketMessageBrokerConfigurer` do behind the scenes
|
|||||||
|
|
||||||
* `WebSocketConnectHandlerDecoratorFactory` is added as a `WebSocketHandlerDecoratorFactory` to `WebSocketTransportRegistration`.
|
* `WebSocketConnectHandlerDecoratorFactory` is added as a `WebSocketHandlerDecoratorFactory` to `WebSocketTransportRegistration`.
|
||||||
This ensures a custom `SessionConnectEvent` is fired that contains the `WebSocketSession`.
|
This ensures a custom `SessionConnectEvent` is fired that contains the `WebSocketSession`.
|
||||||
The `WebSocketSession` is necessary to terminate any WebSocket connections that are still open when a Spring Session is terminated.
|
The `WebSocketSession` is necessary to end any WebSocket connections that are still open when a Spring Session is ended.
|
||||||
* `SessionRepositoryMessageInterceptor` is added as a `HandshakeInterceptor` to every `StompWebSocketEndpointRegistration`.
|
* `SessionRepositoryMessageInterceptor` is added as a `HandshakeInterceptor` to every `StompWebSocketEndpointRegistration`.
|
||||||
This ensures that the `Session` is added to the WebSocket properties to enable updating the last accessed time.
|
This ensures that the `Session` is added to the WebSocket properties to enable updating the last accessed time.
|
||||||
* `SessionRepositoryMessageInterceptor` is added as a `ChannelInterceptor` to our inbound `ChannelRegistration`.
|
* `SessionRepositoryMessageInterceptor` is added as a `ChannelInterceptor` to our inbound `ChannelRegistration`.
|
||||||
This ensures that every time an inbound message is received, that the last accessed time of our Spring Session is updated.
|
This ensures that every time an inbound message is received, that the last accessed time of our Spring Session is updated.
|
||||||
* `WebSocketRegistryListener` is created as a Spring bean.
|
* `WebSocketRegistryListener` is created as a Spring bean.
|
||||||
This ensures that we have a mapping of all of the `Session` IDs to the corresponding WebSocket connections.
|
This ensures that we have a mapping of all of the `Session` IDs to the corresponding WebSocket connections.
|
||||||
By maintaining this mapping, we can close all the WebSocket connections when a Spring Session (HttpSession) is terminated.
|
By maintaining this mapping, we can close all the WebSocket connections when a Spring Session (HttpSession) is ended.
|
||||||
|
|
||||||
|
|
||||||
// end::config[]
|
// end::config[]
|
||||||
@@ -125,7 +131,7 @@ You can see that the message is no longer sent.
|
|||||||
====
|
====
|
||||||
Spring Session expires in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.
|
Spring Session expires in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.
|
||||||
To ensure the socket is closed in a reasonable amount of time, Spring Session runs a background task every minute at 00 seconds that forcibly cleans up any expired sessions.
|
To ensure the socket is closed in a reasonable amount of time, Spring Session runs a background task every minute at 00 seconds that forcibly cleans up any expired sessions.
|
||||||
This means you need to wait at most two minutes before the WebSocket connection is terminated.
|
This means you need to wait at most two minutes before the WebSocket connection is closed.
|
||||||
====
|
====
|
||||||
|
|
||||||
You can now try accessing http://localhost:8080/
|
You can now try accessing http://localhost:8080/
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<script type="text/javascript" src="../js/tocbot/tocbot.min.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/toc.js"></script>
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
= Spring Session - Custom Cookie
|
= Spring Session - Custom Cookie
|
||||||
Rob Winch; Eleftheria Stein-Kousathana
|
Rob Winch; Eleftheria Stein-Kousathana
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to configure Spring Session to use custom cookies with Java Configuration.
|
This guide describes how to configure Spring Session to use custom cookies with Java Configuration.
|
||||||
The guide assumes you have already link:./httpsession.html[set up Spring Session in your project].
|
The guide assumes you have already set up Spring Session in your project using your chosen data store. For example, link:./boot-redis.html[HttpSession with Redis].
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<custom-cookie-sample, Custom Cookie sample application>>.
|
NOTE: You can find the completed guide in the <<custom-cookie-sample, Custom Cookie sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
[[custom-cookie-spring-configuration]]
|
[[custom-cookie-spring-configuration]]
|
||||||
== Spring Java Configuration
|
== Spring Java Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
= Spring Session and Spring Security with Hazelcast
|
= Spring Session and Spring Security with Hazelcast
|
||||||
Tommy Ludwig; Rob Winch
|
Tommy Ludwig; Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session along with Spring Security when you use Hazelcast as your data store.
|
This guide describes how to use Spring Session along with Spring Security when you use Hazelcast as your data store.
|
||||||
It assumes that you have already applied Spring Security to your application.
|
It assumes that you have already applied Spring Security to your application.
|
||||||
|
|
||||||
NOTE: You cand find the completed guide in the <<hazelcast-spring-security-sample, Hazelcast Spring Security sample application>>.
|
NOTE: You cand find the completed guide in the <<hazelcast-spring-security-sample, Hazelcast Spring Security sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
@@ -91,11 +97,18 @@ The filter is in charge of replacing the `HttpSession` implementation to be back
|
|||||||
In this instance, Spring Session is backed by Hazelcast.
|
In this instance, Spring Session is backed by Hazelcast.
|
||||||
<2> In order to support retrieval of sessions by principal name index, an appropriate `ValueExtractor` needs to be registered.
|
<2> In order to support retrieval of sessions by principal name index, an appropriate `ValueExtractor` needs to be registered.
|
||||||
Spring Session provides `PrincipalNameExtractor` for this purpose.
|
Spring Session provides `PrincipalNameExtractor` for this purpose.
|
||||||
<3> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
|
<3> In order to serialize `MapSession` objects efficiently, `HazelcastSessionSerializer` needs to be registered. If this
|
||||||
|
is not set, Hazelcast will serialize sessions using native Java serialization.
|
||||||
|
<4> We create a `HazelcastInstance` that connects Spring Session to Hazelcast.
|
||||||
By default, the application starts and connects to an embedded instance of Hazelcast.
|
By default, the application starts and connects to an embedded instance of Hazelcast.
|
||||||
For more information on configuring Hazelcast, see the https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
|
For more information on configuring Hazelcast, see the https://docs.hazelcast.org/docs/{hazelcast-version}/manual/html-single/index.html#hazelcast-configuration[reference documentation].
|
||||||
====
|
====
|
||||||
|
|
||||||
|
NOTE: If `HazelcastSessionSerializer` is preferred, it needs to be configured for all Hazelcast cluster members before they start.
|
||||||
|
In a Hazelcast cluster, all members should use the same serialization method for sessions. Also, if Hazelcast Client/Server topology
|
||||||
|
is used, then both members and clients must use the same serialization method. The serializer can be registered via `ClientConfig`
|
||||||
|
with the same `SerializerConfiguration` of members.
|
||||||
|
|
||||||
== Servlet Container Initialization
|
== Servlet Container Initialization
|
||||||
|
|
||||||
Our <<security-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
Our <<security-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - HttpSession (Quick Start)
|
= Spring Session - HttpSession (Quick Start)
|
||||||
Rob Winch, Vedran Pavić
|
Rob Winch, Vedran Pavić
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` with Java Configuration.
|
This guide describes how to use Spring Session to transparently leverage a relational database to back a web application's `HttpSession` with Java Configuration.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<httpsession-jdbc-sample, httpsession-jdbc sample application>>.
|
NOTE: You can find the completed guide in the <<httpsession-jdbc-sample, httpsession-jdbc sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
@@ -99,7 +105,7 @@ For additional information on how to configure data access related concerns, see
|
|||||||
|
|
||||||
== Java Servlet Container Initialization
|
== Java Servlet Container Initialization
|
||||||
|
|
||||||
Our <<httpsession-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
Our <<httpsession-jdbc-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||||
@@ -122,6 +128,36 @@ Doing so ensures that the Spring bean named `springSessionRepositoryFilter` is r
|
|||||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to ensure Spring loads our `Config`.
|
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to ensure Spring loads our `Config`.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
== Multiple DataSources
|
||||||
|
Spring Session provides the `@SpringSessionDataSource` qualifier, allowing you to explicitly declare which `DataSource` bean should be injected in `JdbcIndexedSessionRepository`.
|
||||||
|
This is particularly useful in scenarios with multiple `DataSource` beans present in the application context.
|
||||||
|
|
||||||
|
The following example shows how to do so:
|
||||||
|
|
||||||
|
====
|
||||||
|
.Config.java
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@EnableJdbcHttpSession
|
||||||
|
public class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SpringSessionDataSource // <1>
|
||||||
|
public EmbeddedDatabase firstDataSource() {
|
||||||
|
return new EmbeddedDatabaseBuilder()
|
||||||
|
.setType(EmbeddedDatabaseType.H2).addScript("org/springframework/session/jdbc/schema-h2.sql").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HikariDataSource secondDataSource() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> This qualifier declares that firstDataSource is to be used by Spring Session.
|
||||||
|
====
|
||||||
|
|
||||||
// end::config[]
|
// end::config[]
|
||||||
|
|
||||||
[[httpsession-jdbc-sample]]
|
[[httpsession-jdbc-sample]]
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
= Spring Session - HttpSession (Quick Start)
|
= Spring Session - HttpSession (Quick Start)
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
:version-snapshot: true
|
:version-snapshot: true
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with Java Configuration.
|
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with Java Configuration.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<httpsession-sample, httpsession sample application>>.
|
NOTE: You can find the completed guide in the <<httpsession-sample, httpsession sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
If you are using Maven, you must add the following dependencies:
|
If you are using Maven, you must add the following dependencies:
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - REST
|
= Spring Session - REST
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use REST endpoints.
|
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when you use REST endpoints.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<rest-sample, rest sample application>>.
|
NOTE: You can find the completed guide in the <<rest-sample, rest sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
@@ -241,7 +247,7 @@ $ curl -v http://localhost:8080/ -u user:password
|
|||||||
|
|
||||||
In the output, you should notice the following:
|
In the output, you should notice the following:
|
||||||
|
|
||||||
===
|
====
|
||||||
----
|
----
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
= Spring Session and Spring Security
|
= Spring Session and Spring Security
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session along with Spring Security.
|
This guide describes how to use Spring Session along with Spring Security.
|
||||||
It assumes you have already applied Spring Security to your application.
|
It assumes you have already applied Spring Security to your application.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<security-sample, security sample application>>.
|
NOTE: You can find the completed guide in the <<security-sample, security sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
If you use Maven, you must add the following dependencies:
|
If you use Maven, you must add the following dependencies:
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - HttpSession (Quick Start)
|
= Spring Session - HttpSession (Quick Start)
|
||||||
Rob Winch, Vedran Pavić
|
Rob Winch, Vedran Pavić
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage a relational to back a web application's `HttpSession` with XML based configuration.
|
This guide describes how to use Spring Session to transparently leverage a relational to back a web application's `HttpSession` with XML based configuration.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<httpsession-jdbc-xml-sample, httpsession-jdbc-xml sample application>>.
|
NOTE: You can find the completed guide in the <<httpsession-jdbc-xml-sample, httpsession-jdbc-xml sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
|
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
@@ -68,8 +74,8 @@ You must have the following in your pom.xml:
|
|||||||
<url>https://repo.spring.io/libs-milestone</url>
|
<url>https://repo.spring.io/libs-milestone</url>
|
||||||
</repository>
|
</repository>
|
||||||
----
|
----
|
||||||
endif::[]
|
|
||||||
====
|
====
|
||||||
|
endif::[]
|
||||||
|
|
||||||
// tag::config[]
|
// tag::config[]
|
||||||
|
|
||||||
@@ -101,7 +107,7 @@ For additional information on how to configure data access-related concerns, see
|
|||||||
|
|
||||||
== XML Servlet Container Initialization
|
== XML Servlet Container Initialization
|
||||||
|
|
||||||
Our <<httpsession-xml-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
Our <<httpsession-jdbc-xml-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||||
|
|
||||||
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration.
|
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration.
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
= Spring Session - HttpSession (Quick Start)
|
= Spring Session - HttpSession (Quick Start)
|
||||||
Rob Winch
|
Rob Winch
|
||||||
:toc:
|
:toc: left
|
||||||
|
:stylesdir: ../
|
||||||
|
:highlightjsdir: ../js/highlight
|
||||||
|
:docinfodir: guides
|
||||||
|
|
||||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with XML-based configuration.
|
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` with XML-based configuration.
|
||||||
|
|
||||||
NOTE: You can find the completed guide in the <<httpsession-xml-sample, httpsession-xml sample application>>.
|
NOTE: You can find the completed guide in the <<httpsession-xml-sample, httpsession-xml sample application>>.
|
||||||
|
|
||||||
|
[#index-link]
|
||||||
|
link:../index.html[Index]
|
||||||
|
|
||||||
== Updating Dependencies
|
== Updating Dependencies
|
||||||
Before you use Spring Session, you must update your dependencies.
|
Before you use Spring Session, you must update your dependencies.
|
||||||
If you use Maven, you must add the following dependencies:
|
If you use Maven, you must add the following dependencies:
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ To get started with Spring Session, the best place to start is our Sample Applic
|
|||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||||
| link:guides/boot-jdbc.html[HttpSession with JDBC Guide]
|
| link:guides/boot-jdbc.html[HttpSession with JDBC Guide]
|
||||||
|
|
||||||
|
| {gh-samples-url}spring-session-sample-boot-hazelcast[HttpSession with Hazelcast]
|
||||||
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Hazelcast.
|
||||||
|
|
|
||||||
|
|
||||||
| {gh-samples-url}spring-session-sample-boot-findbyusername[Find by Username]
|
| {gh-samples-url}spring-session-sample-boot-findbyusername[Find by Username]
|
||||||
| Demonstrates how to use Spring Session to find sessions by username.
|
| Demonstrates how to use Spring Session to find sessions by username.
|
||||||
| link:guides/boot-findbyusername.html[Find by Username Guide]
|
| link:guides/boot-findbyusername.html[Find by Username Guide]
|
||||||
@@ -70,6 +74,10 @@ To get started with Spring Session, the best place to start is our Sample Applic
|
|||||||
| Demonstrates how to use Spring Session to replace the Spring WebFlux's `WebSession` with Redis.
|
| Demonstrates how to use Spring Session to replace the Spring WebFlux's `WebSession` with Redis.
|
||||||
|
|
|
|
||||||
|
|
||||||
|
| {gh-samples-url}spring-session-sample-boot-webflux-custom-cookie[WebFlux with Custom Cookie]
|
||||||
|
| Demonstrates how to use Spring Session to customize the Session cookie in a WebFlux based application.
|
||||||
|
| link:guides/boot-webflux-custom-cookie.html[WebFlux with Custom Cookie Guide]
|
||||||
|
|
||||||
| {gh-samples-url}spring-session-sample-boot-redis-json[HttpSession with Redis JSON serialization]
|
| {gh-samples-url}spring-session-sample-boot-redis-json[HttpSession with Redis JSON serialization]
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization.
|
||||||
|
|
|
|
||||||
@@ -289,7 +297,7 @@ Any method that returns an `HttpSession` is overridden.
|
|||||||
All other methods are implemented by `HttpServletRequestWrapper` and delegate to the original `HttpServletRequest` implementation.
|
All other methods are implemented by `HttpServletRequestWrapper` and delegate to the original `HttpServletRequest` implementation.
|
||||||
|
|
||||||
We replace the `HttpServletRequest` implementation by using a servlet `Filter` called `SessionRepositoryFilter`.
|
We replace the `HttpServletRequest` implementation by using a servlet `Filter` called `SessionRepositoryFilter`.
|
||||||
The pseudocode belows:
|
The following pseudocode shows how it works:
|
||||||
|
|
||||||
====
|
====
|
||||||
[source, java]
|
[source, java]
|
||||||
@@ -499,7 +507,7 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
|||||||
====
|
====
|
||||||
|
|
||||||
To be detected by Spring WebFlux, this custom `WebSessionStore` needs to be registered with `ApplicationContext` as a bean named `webSessionManager`.
|
To be detected by Spring WebFlux, this custom `WebSessionStore` needs to be registered with `ApplicationContext` as a bean named `webSessionManager`.
|
||||||
For additional information on Spring WebFlux, see the https://docs.spring.io/spring/docs/{spring-framework-version}/spring-framework-reference/web-reactive.html[Spring Framework Reference Documentation].
|
For additional information on Spring WebFlux, see the https://docs.spring.io/spring-framework/docs/{spring-framework-version}/reference/html/web-reactive.html[Spring Framework Reference Documentation].
|
||||||
|
|
||||||
[[spring-security]]
|
[[spring-security]]
|
||||||
== Spring Security Integration
|
== Spring Security Integration
|
||||||
@@ -783,7 +791,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
|
|||||||
maxInactiveInterval 1800 \
|
maxInactiveInterval 1800 \
|
||||||
lastAccessedTime 1404360000000 \
|
lastAccessedTime 1404360000000 \
|
||||||
sessionAttr:attrName someAttrValue \
|
sessionAttr:attrName someAttrValue \
|
||||||
sessionAttr2:attrName someAttrValue2
|
sessionAttr:attrName2 someAttrValue2
|
||||||
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
|
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
|
||||||
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
|
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
|
||||||
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
|
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
|
||||||
@@ -806,7 +814,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
|
|||||||
maxInactiveInterval 1800 \
|
maxInactiveInterval 1800 \
|
||||||
lastAccessedTime 1404360000000 \
|
lastAccessedTime 1404360000000 \
|
||||||
sessionAttr:attrName someAttrValue \
|
sessionAttr:attrName someAttrValue \
|
||||||
sessionAttr2:attrName someAttrValue2
|
sessionAttr:attrName2 someAttrValue2
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
@@ -825,7 +833,7 @@ The second session attribute is named `attrName2`, with a value of `someAttrValu
|
|||||||
|
|
||||||
The `Session` instances managed by `RedisIndexedSessionRepository` keeps track of the properties that have changed and updates only those.
|
The `Session` instances managed by `RedisIndexedSessionRepository` keeps track of the properties that have changed and updates only those.
|
||||||
This means that, if an attribute is written once and read many times, we need to write that attribute only once.
|
This means that, if an attribute is written once and read many times, we need to write that attribute only once.
|
||||||
For example, assume the `sessionAttr2` session attribute from the lsiting in the preceding section was updated.
|
For example, assume the `attrName2` session attribute from the lsiting in the preceding section was updated.
|
||||||
The following command would be run upon saving:
|
The following command would be run upon saving:
|
||||||
|
|
||||||
====
|
====
|
||||||
@@ -1173,8 +1181,8 @@ include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schem
|
|||||||
|
|
||||||
==== Transaction Management
|
==== Transaction Management
|
||||||
|
|
||||||
All JDBC operations in `JdbcIndexedSessionRepository` are executed in a transactional manner.
|
All JDBC operations in `JdbcIndexedSessionRepository` are performed in a transactional manner.
|
||||||
Transactions are executed with propagation set to `REQUIRES_NEW` in order to avoid unexpected behavior due to interference with existing transactions (for example, running a `save` operation in a thread that already participates in a read-only transaction).
|
Transactions are performed with propagation set to `REQUIRES_NEW` in order to avoid unexpected behavior due to interference with existing transactions (for example, running a `save` operation in a thread that already participates in a read-only transaction).
|
||||||
|
|
||||||
[[api-hazelcastindexedsessionrepository]]
|
[[api-hazelcastindexedsessionrepository]]
|
||||||
=== Using `HazelcastIndexedSessionRepository`
|
=== Using `HazelcastIndexedSessionRepository`
|
||||||
@@ -1294,7 +1302,7 @@ WARNING: You should only match on valid domain characters, since the domain name
|
|||||||
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
Doing so prevents a malicious user from performing such attacks as https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||||
|
|
||||||
[[custom-sessionrepository]]
|
[[custom-sessionrepository]]
|
||||||
== Customing `SessionRepository`
|
== Customizing `SessionRepository`
|
||||||
|
|
||||||
Implementing a custom <<api-sessionrepository,`SessionRepository`>> API should be a fairly straightforward task.
|
Implementing a custom <<api-sessionrepository,`SessionRepository`>> API should be a fairly straightforward task.
|
||||||
Coupling the custom implementation with <<api-enablespringhttpsession,`@EnableSpringHttpSession`>> support lets you reuse existing Spring Session configuration facilities and infrastructure.
|
Coupling the custom implementation with <<api-enablespringhttpsession,`@EnableSpringHttpSession`>> support lets you reuse existing Spring Session configuration facilities and infrastructure.
|
||||||
@@ -1398,11 +1406,8 @@ Spring Session is Open Source software released under the https://www.apache.org
|
|||||||
|===
|
|===
|
||||||
| Name | Location
|
| Name | Location
|
||||||
|
|
||||||
| Spring Session OrientDB
|
|
||||||
| https://github.com/maseev/spring-session-orientdb
|
|
||||||
|
|
||||||
| Spring Session Infinispan
|
| Spring Session Infinispan
|
||||||
| https://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session
|
| https://infinispan.org/infinispan-spring-boot/master/spring_boot_starter.html#_enabling_spring_session_support
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,15 @@ package docs.http;
|
|||||||
import com.hazelcast.config.Config;
|
import com.hazelcast.config.Config;
|
||||||
import com.hazelcast.config.MapAttributeConfig;
|
import com.hazelcast.config.MapAttributeConfig;
|
||||||
import com.hazelcast.config.MapIndexConfig;
|
import com.hazelcast.config.MapIndexConfig;
|
||||||
|
import com.hazelcast.config.SerializerConfig;
|
||||||
import com.hazelcast.core.Hazelcast;
|
import com.hazelcast.core.Hazelcast;
|
||||||
import com.hazelcast.core.HazelcastInstance;
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||||
|
import org.springframework.session.hazelcast.HazelcastSessionSerializer;
|
||||||
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
import org.springframework.session.hazelcast.PrincipalNameExtractor;
|
||||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||||
|
|
||||||
@@ -42,7 +45,10 @@ public class HazelcastHttpSessionConfig {
|
|||||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
|
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
|
||||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||||
return Hazelcast.newHazelcastInstance(config); // <3>
|
SerializerConfig serializerConfig = new SerializerConfig();
|
||||||
|
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||||
|
config.getSerializationConfig().addSerializerConfig(serializerConfig); // <3>
|
||||||
|
return Hazelcast.newHazelcastInstance(config); // <4>
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package docs.security;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
@@ -41,14 +42,16 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
|
|||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ... additional configuration ...
|
// ... additional configuration ...
|
||||||
.rememberMe()
|
.rememberMe((rememberMe) -> rememberMe
|
||||||
.rememberMeServices(rememberMeServices());
|
.rememberMeServices(rememberMeServices())
|
||||||
|
);
|
||||||
// end::http-rememberme[]
|
// end::http-rememberme[]
|
||||||
|
|
||||||
http
|
http
|
||||||
.formLogin().and()
|
.formLogin(Customizer.withDefaults())
|
||||||
.authorizeRequests()
|
.authorizeRequests((authorize) -> authorize
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tag::rememberme-bean[]
|
// tag::rememberme-bean[]
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ public class SecurityConfiguration<S extends Session> extends WebSecurityConfigu
|
|||||||
// @formatter:off
|
// @formatter:off
|
||||||
http
|
http
|
||||||
// other config goes here...
|
// other config goes here...
|
||||||
.sessionManagement()
|
.sessionManagement((sessionManagement) -> sessionManagement
|
||||||
.maximumSessions(2)
|
.maximumSessions(2)
|
||||||
.sessionRegistry(sessionRegistry());
|
.sessionRegistry(sessionRegistry())
|
||||||
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
spring-session-hazelcast/hazelcast4/hazelcast4.gradle
Normal file
48
spring-session-hazelcast/hazelcast4/hazelcast4.gradle
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
maven { url 'https://repo.spring.io/plugins-release' }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'io.spring.convention.repository'
|
||||||
|
id 'io.spring.convention.springdependencymangement'
|
||||||
|
id 'io.spring.convention.dependency-set'
|
||||||
|
id 'io.spring.convention.checkstyle'
|
||||||
|
id 'io.spring.convention.tests-configuration'
|
||||||
|
id 'io.spring.convention.integration-test'
|
||||||
|
id 'propdeps'
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
classesOnlyElements {
|
||||||
|
canBeConsumed = true
|
||||||
|
canBeResolved = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
classesOnlyElements(compileJava.destinationDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':spring-session-core')
|
||||||
|
optional "com.hazelcast:hazelcast:4.2"
|
||||||
|
compile "org.springframework:spring-context"
|
||||||
|
compile "javax.annotation:javax.annotation-api"
|
||||||
|
|
||||||
|
testCompile "javax.servlet:javax.servlet-api"
|
||||||
|
testCompile "org.springframework:spring-web"
|
||||||
|
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||||
|
testCompile "org.springframework.security:spring-security-core"
|
||||||
|
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||||
|
|
||||||
|
integrationTestCompile "org.testcontainers:testcontainers"
|
||||||
|
integrationTestCompile "com.hazelcast:hazelcast:4.2"
|
||||||
|
integrationTestCompile project(":spring-session-hazelcast")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import com.hazelcast.map.IMap;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for {@link Hazelcast4IndexedSessionRepository} integration tests.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
abstract class AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Hazelcast4IndexedSessionRepository repository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAndDestroySession() {
|
||||||
|
HazelcastSession sessionToSave = this.repository.createSession();
|
||||||
|
String sessionId = sessionToSave.getId();
|
||||||
|
|
||||||
|
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||||
|
.getMap(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
|
|
||||||
|
assertThat(hazelcastMap.get(sessionId)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void changeSessionIdWhenOnlyChangeId() {
|
||||||
|
String attrName = "changeSessionId";
|
||||||
|
String attrValue = "changeSessionId-value";
|
||||||
|
HazelcastSession toSave = this.repository.createSession();
|
||||||
|
toSave.setAttribute(attrName, attrValue);
|
||||||
|
|
||||||
|
this.repository.save(toSave);
|
||||||
|
|
||||||
|
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||||
|
|
||||||
|
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||||
|
|
||||||
|
String originalFindById = findById.getId();
|
||||||
|
String changeSessionId = findById.changeSessionId();
|
||||||
|
|
||||||
|
this.repository.save(findById);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||||
|
|
||||||
|
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||||
|
|
||||||
|
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||||
|
|
||||||
|
this.repository.deleteById(changeSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void changeSessionIdWhenChangeTwice() {
|
||||||
|
HazelcastSession toSave = this.repository.createSession();
|
||||||
|
|
||||||
|
this.repository.save(toSave);
|
||||||
|
|
||||||
|
String originalId = toSave.getId();
|
||||||
|
String changeId1 = toSave.changeSessionId();
|
||||||
|
String changeId2 = toSave.changeSessionId();
|
||||||
|
|
||||||
|
this.repository.save(toSave);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(originalId)).isNull();
|
||||||
|
assertThat(this.repository.findById(changeId1)).isNull();
|
||||||
|
assertThat(this.repository.findById(changeId2)).isNotNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(changeId2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void changeSessionIdWhenSetAttributeOnChangedSession() {
|
||||||
|
String attrName = "changeSessionId";
|
||||||
|
String attrValue = "changeSessionId-value";
|
||||||
|
|
||||||
|
HazelcastSession toSave = this.repository.createSession();
|
||||||
|
|
||||||
|
this.repository.save(toSave);
|
||||||
|
|
||||||
|
HazelcastSession findById = this.repository.findById(toSave.getId());
|
||||||
|
|
||||||
|
findById.setAttribute(attrName, attrValue);
|
||||||
|
|
||||||
|
String originalFindById = findById.getId();
|
||||||
|
String changeSessionId = findById.changeSessionId();
|
||||||
|
|
||||||
|
this.repository.save(findById);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||||
|
|
||||||
|
HazelcastSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||||
|
|
||||||
|
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||||
|
|
||||||
|
this.repository.deleteById(changeSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void changeSessionIdWhenHasNotSaved() {
|
||||||
|
HazelcastSession toSave = this.repository.createSession();
|
||||||
|
String originalId = toSave.getId();
|
||||||
|
toSave.changeSessionId();
|
||||||
|
|
||||||
|
this.repository.save(toSave);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(toSave.getId())).isNotNull();
|
||||||
|
assertThat(this.repository.findById(originalId)).isNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(toSave.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // gh-1076
|
||||||
|
void attemptToUpdateSessionAfterDelete() {
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
String sessionId = session.getId();
|
||||||
|
this.repository.save(session);
|
||||||
|
session = this.repository.findById(sessionId);
|
||||||
|
session.setAttribute("attributeName", "attributeValue");
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
|
this.repository.save(session);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionId)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAndUpdateSession() {
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
String sessionId = session.getId();
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
|
||||||
|
session = this.repository.findById(sessionId);
|
||||||
|
session.setAttribute("attributeName", "attributeValue");
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createSessionWithSecurityContextAndFindById() {
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
String sessionId = session.getId();
|
||||||
|
|
||||||
|
Authentication authentication = new UsernamePasswordAuthenticationToken("saves-" + System.currentTimeMillis(),
|
||||||
|
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||||
|
securityContext.setAuthentication(authentication);
|
||||||
|
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import com.hazelcast.client.HazelcastClient;
|
||||||
|
import com.hazelcast.client.config.ClientConfig;
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link Hazelcast4IndexedSessionRepository} using client-server
|
||||||
|
* topology.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@WebAppConfiguration
|
||||||
|
class ClientServerHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.2").withExposedPorts(5701)
|
||||||
|
.withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||||
|
"/opt/hazelcast/hazelcast.xml");
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUpClass() {
|
||||||
|
container.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDownClass() {
|
||||||
|
container.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableHazelcastHttpSession
|
||||||
|
static class HazelcastSessionConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
HazelcastInstance hazelcastInstance() {
|
||||||
|
ClientConfig clientConfig = new ClientConfig();
|
||||||
|
clientConfig.getNetworkConfig()
|
||||||
|
.addAddress(container.getContainerIpAddress() + ":" + container.getFirstMappedPort());
|
||||||
|
clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
|
||||||
|
.addClass(MapSession.class).addClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||||
|
return HazelcastClient.newHazelcastClient(clientConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link Hazelcast4IndexedSessionRepository} using embedded
|
||||||
|
* topology.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@WebAppConfiguration
|
||||||
|
class EmbeddedHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@EnableHazelcastHttpSession
|
||||||
|
@Configuration
|
||||||
|
static class HazelcastSessionConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
HazelcastInstance hazelcastInstance() {
|
||||||
|
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import com.hazelcast.config.AttributeConfig;
|
||||||
|
import com.hazelcast.config.Config;
|
||||||
|
import com.hazelcast.config.IndexConfig;
|
||||||
|
import com.hazelcast.config.IndexType;
|
||||||
|
import com.hazelcast.config.NetworkConfig;
|
||||||
|
import com.hazelcast.config.SerializerConfig;
|
||||||
|
import com.hazelcast.core.Hazelcast;
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for Hazelcast integration tests.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
final class Hazelcast4ITestUtils {
|
||||||
|
|
||||||
|
private Hazelcast4ITestUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates {@link HazelcastInstance} for use in integration tests.
|
||||||
|
* @return the Hazelcast instance
|
||||||
|
*/
|
||||||
|
static HazelcastInstance embeddedHazelcastServer() {
|
||||||
|
Config config = new Config();
|
||||||
|
NetworkConfig networkConfig = config.getNetworkConfig();
|
||||||
|
networkConfig.setPort(0);
|
||||||
|
networkConfig.getJoin().getMulticastConfig().setEnabled(false);
|
||||||
|
AttributeConfig attributeConfig = new AttributeConfig()
|
||||||
|
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||||
|
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||||
|
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||||
|
.addAttributeConfig(attributeConfig).addIndexConfig(
|
||||||
|
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||||
|
SerializerConfig serializerConfig = new SerializerConfig();
|
||||||
|
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||||
|
config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||||
|
return Hazelcast.newHazelcastInstance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
import org.springframework.session.events.SessionCreatedEvent;
|
||||||
|
import org.springframework.session.events.SessionDeletedEvent;
|
||||||
|
import org.springframework.session.events.SessionExpiredEvent;
|
||||||
|
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the appropriate SessionEvents are fired at the expected times. Additionally
|
||||||
|
* ensure that the interactions with the {@link SessionRepository} abstraction behave as
|
||||||
|
* expected after each SessionEvent.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@WebAppConfiguration
|
||||||
|
class SessionEventHazelcast4IndexedSessionRepositoryTests<S extends Session> {
|
||||||
|
|
||||||
|
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SessionRepository<S> repository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SessionEventRegistry registry;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
this.registry.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveSessionTest() throws InterruptedException {
|
||||||
|
String username = "saves-" + System.currentTimeMillis();
|
||||||
|
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
|
||||||
|
String expectedAttributeName = "a";
|
||||||
|
String expectedAttributeValue = "b";
|
||||||
|
sessionToSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||||
|
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username, "password",
|
||||||
|
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||||
|
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||||
|
toSaveContext.setAuthentication(toSaveToken);
|
||||||
|
sessionToSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
|
||||||
|
sessionToSave.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
|
||||||
|
Session session = this.repository.findById(sessionToSave.getId());
|
||||||
|
|
||||||
|
assertThat(session.getId()).isEqualTo(sessionToSave.getId());
|
||||||
|
assertThat(session.getAttributeNames()).isEqualTo(sessionToSave.getAttributeNames());
|
||||||
|
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||||
|
.isEqualTo(sessionToSave.getAttribute(expectedAttributeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void expiredSessionTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
this.registry.clear();
|
||||||
|
|
||||||
|
assertThat(sessionToSave.getMaxInactiveInterval())
|
||||||
|
.isEqualTo(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionExpiredEvent.class);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deletedSessionTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
this.registry.clear();
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionToSave.getId());
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionDeletedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionDeletedEvent.class);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionToSave.getId())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatesTimeToLiveTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
sessionToSave.setMaxInactiveInterval(Duration.ofSeconds(3));
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
// Get and save the session like SessionRepositoryFilter would.
|
||||||
|
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||||
|
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||||
|
this.repository.save(sessionToUpdate);
|
||||||
|
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(sessionToUpdate.getId())).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // gh-1077
|
||||||
|
void changeSessionIdNoEventTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||||
|
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
this.registry.clear();
|
||||||
|
|
||||||
|
sessionToSave.changeSessionId();
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // gh-1300
|
||||||
|
void updateMaxInactiveIntervalTest() throws InterruptedException {
|
||||||
|
S sessionToSave = this.repository.createSession();
|
||||||
|
sessionToSave.setMaxInactiveInterval(Duration.ofMinutes(30));
|
||||||
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToSave.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionCreatedEvent>getEvent(sessionToSave.getId()))
|
||||||
|
.isInstanceOf(SessionCreatedEvent.class);
|
||||||
|
this.registry.clear();
|
||||||
|
|
||||||
|
S sessionToUpdate = this.repository.findById(sessionToSave.getId());
|
||||||
|
sessionToUpdate.setLastAccessedTime(Instant.now());
|
||||||
|
sessionToUpdate.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||||
|
this.repository.save(sessionToUpdate);
|
||||||
|
|
||||||
|
assertThat(this.registry.receivedEvent(sessionToUpdate.getId())).isTrue();
|
||||||
|
assertThat(this.registry.<SessionExpiredEvent>getEvent(sessionToUpdate.getId()))
|
||||||
|
.isInstanceOf(SessionExpiredEvent.class);
|
||||||
|
assertThat(this.repository.findById(sessionToUpdate.getId())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||||
|
static class HazelcastSessionConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
HazelcastInstance embeddedHazelcast() {
|
||||||
|
return Hazelcast4ITestUtils.embeddedHazelcastServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SessionEventRegistry sessionEventRegistry() {
|
||||||
|
return new SessionEventRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.session.events.AbstractSessionEvent;
|
||||||
|
|
||||||
|
class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||||
|
|
||||||
|
private Map<String, AbstractSessionEvent> events = new HashMap<>();
|
||||||
|
|
||||||
|
private ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||||
|
String sessionId = event.getSessionId();
|
||||||
|
this.events.put(sessionId, event);
|
||||||
|
Object lock = getLock(sessionId);
|
||||||
|
synchronized (lock) {
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
this.events.clear();
|
||||||
|
this.locks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||||
|
return waitForEvent(sessionId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
<E extends AbstractSessionEvent> E getEvent(String sessionId) throws InterruptedException {
|
||||||
|
return (E) waitForEvent(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <E extends AbstractSessionEvent> E waitForEvent(String sessionId) throws InterruptedException {
|
||||||
|
Object lock = getLock(sessionId);
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!this.events.containsKey(sessionId)) {
|
||||||
|
lock.wait(10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (E) this.events.get(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getLock(String sessionId) {
|
||||||
|
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-4.1.xsd">
|
||||||
|
|
||||||
|
<network>
|
||||||
|
<join>
|
||||||
|
<multicast enabled="false"/>
|
||||||
|
</join>
|
||||||
|
</network>
|
||||||
|
|
||||||
|
<user-code-deployment enabled="true">
|
||||||
|
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||||
|
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
|
||||||
|
</user-code-deployment>
|
||||||
|
|
||||||
|
</hazelcast>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ryuk.container.timeout=120
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
|
||||||
|
import com.hazelcast.core.EntryEvent;
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import com.hazelcast.map.IMap;
|
||||||
|
import com.hazelcast.map.listener.EntryAddedListener;
|
||||||
|
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||||
|
import com.hazelcast.map.listener.EntryExpiredListener;
|
||||||
|
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||||
|
import com.hazelcast.query.Predicates;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.session.DelegatingIndexResolver;
|
||||||
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.FlushMode;
|
||||||
|
import org.springframework.session.IndexResolver;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.PrincipalNameIndexResolver;
|
||||||
|
import org.springframework.session.SaveMode;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.events.AbstractSessionEvent;
|
||||||
|
import org.springframework.session.events.SessionCreatedEvent;
|
||||||
|
import org.springframework.session.events.SessionDeletedEvent;
|
||||||
|
import org.springframework.session.events.SessionExpiredEvent;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.springframework.session.SessionRepository} implementation using Hazelcast
|
||||||
|
* 4 that stores sessions in Hazelcast's distributed {@link IMap}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An example of how to create a new instance can be seen below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* Config config = new Config();
|
||||||
|
*
|
||||||
|
* // ... configure Hazelcast ...
|
||||||
|
*
|
||||||
|
* HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||||
|
*
|
||||||
|
* Hazelcast4IndexedSessionRepository sessionRepository =
|
||||||
|
* new Hazelcast4IndexedSessionRepository(hazelcastInstance);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* In order to support finding sessions by principal name using
|
||||||
|
* {@link #findByIndexNameAndIndexValue(String, String)} method, custom configuration of
|
||||||
|
* {@code IMap} supplied to this implementation is required.
|
||||||
|
*
|
||||||
|
* The following snippet demonstrates how to define required configuration using
|
||||||
|
* programmatic Hazelcast Configuration:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* AttributeConfig attributeConfig = new AttributeConfig()
|
||||||
|
* .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
|
||||||
|
* .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
|
||||||
|
*
|
||||||
|
* Config config = new Config();
|
||||||
|
*
|
||||||
|
* config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||||
|
* .addAttributeConfig(attributeConfig)
|
||||||
|
* .addIndexConfig(new IndexConfig(
|
||||||
|
* IndexType.HASH,
|
||||||
|
* Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
|
||||||
|
*
|
||||||
|
* Hazelcast.newHazelcastInstance(config);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* This implementation listens for events on the Hazelcast-backed SessionRepository and
|
||||||
|
* translates those events into the corresponding Spring Session events. Publish the
|
||||||
|
* Spring Session events with the given {@link ApplicationEventPublisher}.
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>entryAdded - {@link SessionCreatedEvent}</li>
|
||||||
|
* <li>entryEvicted - {@link SessionExpiredEvent}</li>
|
||||||
|
* <li>entryExpired - {@link SessionExpiredEvent}</li>
|
||||||
|
* <li>entryRemoved - {@link SessionDeletedEvent}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
public class Hazelcast4IndexedSessionRepository
|
||||||
|
implements FindByIndexNameSessionRepository<Hazelcast4IndexedSessionRepository.HazelcastSession>,
|
||||||
|
EntryAddedListener<String, MapSession>, EntryEvictedListener<String, MapSession>,
|
||||||
|
EntryRemovedListener<String, MapSession>, EntryExpiredListener<String, MapSession> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default name of map used by Spring Session to store sessions.
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_SESSION_MAP_NAME = "spring:session:sessions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The principal name custom attribute name.
|
||||||
|
*/
|
||||||
|
public static final String PRINCIPAL_NAME_ATTRIBUTE = "principalName";
|
||||||
|
|
||||||
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
|
private static final boolean SUPPORTS_SET_TTL = ClassUtils.hasAtLeastOneMethodWithName(IMap.class, "setTtl");
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(Hazelcast4IndexedSessionRepository.class);
|
||||||
|
|
||||||
|
private final HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
private ApplicationEventPublisher eventPublisher = (event) -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If non-null, this value is used to override
|
||||||
|
* {@link MapSession#setMaxInactiveInterval(Duration)}.
|
||||||
|
*/
|
||||||
|
private Integer defaultMaxInactiveInterval;
|
||||||
|
|
||||||
|
private IndexResolver<Session> indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||||
|
|
||||||
|
private String sessionMapName = DEFAULT_SESSION_MAP_NAME;
|
||||||
|
|
||||||
|
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||||
|
|
||||||
|
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||||
|
|
||||||
|
private IMap<String, MapSession> sessions;
|
||||||
|
|
||||||
|
private UUID sessionListenerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link Hazelcast4IndexedSessionRepository} instance.
|
||||||
|
* @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions
|
||||||
|
*/
|
||||||
|
public Hazelcast4IndexedSessionRepository(HazelcastInstance hazelcastInstance) {
|
||||||
|
Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null");
|
||||||
|
this.hazelcastInstance = hazelcastInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
this.sessions = this.hazelcastInstance.getMap(this.sessionMapName);
|
||||||
|
this.sessionListenerId = this.sessions.addEntryListener(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void close() {
|
||||||
|
this.sessions.removeEntryListener(this.sessionListenerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ApplicationEventPublisher} that is used to publish
|
||||||
|
* {@link AbstractSessionEvent session events}. The default is to not publish session
|
||||||
|
* events.
|
||||||
|
* @param applicationEventPublisher the {@link ApplicationEventPublisher} that is used
|
||||||
|
* to publish session events. Cannot be null.
|
||||||
|
*/
|
||||||
|
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||||
|
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher cannot be null");
|
||||||
|
this.eventPublisher = applicationEventPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum inactive interval in seconds between requests before newly created
|
||||||
|
* sessions will be invalidated. A negative time indicates that the session will never
|
||||||
|
* timeout. The default is 1800 (30 minutes).
|
||||||
|
* @param defaultMaxInactiveInterval the maximum inactive interval in seconds
|
||||||
|
*/
|
||||||
|
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
|
||||||
|
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link IndexResolver} to use.
|
||||||
|
* @param indexResolver the index resolver
|
||||||
|
*/
|
||||||
|
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||||
|
Assert.notNull(indexResolver, "indexResolver cannot be null");
|
||||||
|
this.indexResolver = indexResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of map used to store sessions.
|
||||||
|
* @param sessionMapName the session map name
|
||||||
|
*/
|
||||||
|
public void setSessionMapName(String sessionMapName) {
|
||||||
|
Assert.hasText(sessionMapName, "Map name must not be empty");
|
||||||
|
this.sessionMapName = sessionMapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Hazelcast flush mode. Default flush mode is {@link FlushMode#ON_SAVE}.
|
||||||
|
* @param flushMode the new Hazelcast flush mode
|
||||||
|
*/
|
||||||
|
public void setFlushMode(FlushMode flushMode) {
|
||||||
|
Assert.notNull(flushMode, "flushMode cannot be null");
|
||||||
|
this.flushMode = flushMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the save mode.
|
||||||
|
* @param saveMode the save mode
|
||||||
|
*/
|
||||||
|
public void setSaveMode(SaveMode saveMode) {
|
||||||
|
Assert.notNull(saveMode, "saveMode must not be null");
|
||||||
|
this.saveMode = saveMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HazelcastSession createSession() {
|
||||||
|
MapSession cached = new MapSession();
|
||||||
|
if (this.defaultMaxInactiveInterval != null) {
|
||||||
|
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||||
|
}
|
||||||
|
HazelcastSession session = new HazelcastSession(cached, true);
|
||||||
|
session.flushImmediateIfNecessary();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(HazelcastSession session) {
|
||||||
|
if (session.isNew) {
|
||||||
|
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
else if (session.sessionIdChanged) {
|
||||||
|
this.sessions.delete(session.originalId);
|
||||||
|
session.originalId = session.getId();
|
||||||
|
this.sessions.set(session.getId(), session.getDelegate(), session.getMaxInactiveInterval().getSeconds(),
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
else if (session.hasChanges()) {
|
||||||
|
Hazelcast4SessionUpdateEntryProcessor entryProcessor = new Hazelcast4SessionUpdateEntryProcessor();
|
||||||
|
if (session.lastAccessedTimeChanged) {
|
||||||
|
entryProcessor.setLastAccessedTime(session.getLastAccessedTime());
|
||||||
|
}
|
||||||
|
if (session.maxInactiveIntervalChanged) {
|
||||||
|
if (SUPPORTS_SET_TTL) {
|
||||||
|
updateTtl(session);
|
||||||
|
}
|
||||||
|
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||||
|
}
|
||||||
|
if (!session.delta.isEmpty()) {
|
||||||
|
entryProcessor.setDelta(new HashMap<>(session.delta));
|
||||||
|
}
|
||||||
|
this.sessions.executeOnKey(session.getId(), entryProcessor);
|
||||||
|
}
|
||||||
|
session.clearChangeFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTtl(HazelcastSession session) {
|
||||||
|
this.sessions.setTtl(session.getId(), session.getMaxInactiveInterval().getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HazelcastSession findById(String id) {
|
||||||
|
MapSession saved = this.sessions.get(id);
|
||||||
|
if (saved == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (saved.isExpired()) {
|
||||||
|
deleteById(saved.getId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new HazelcastSession(saved, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteById(String id) {
|
||||||
|
this.sessions.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, HazelcastSession> findByIndexNameAndIndexValue(String indexName, String indexValue) {
|
||||||
|
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
Collection<MapSession> sessions = this.sessions.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
|
||||||
|
Map<String, HazelcastSession> sessionMap = new HashMap<>(sessions.size());
|
||||||
|
for (MapSession session : sessions) {
|
||||||
|
sessionMap.put(session.getId(), new HazelcastSession(session, false));
|
||||||
|
}
|
||||||
|
return sessionMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryAdded(EntryEvent<String, MapSession> event) {
|
||||||
|
MapSession session = event.getValue();
|
||||||
|
if (session.getId().equals(session.getOriginalId())) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session created with id: " + session.getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionCreatedEvent(this, session));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryEvicted(EntryEvent<String, MapSession> event) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryRemoved(EntryEvent<String, MapSession> event) {
|
||||||
|
MapSession session = event.getOldValue();
|
||||||
|
if (session != null) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session deleted with id: " + session.getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionDeletedEvent(this, session));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entryExpired(EntryEvent<String, MapSession> event) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Session expired with id: " + event.getOldValue().getId());
|
||||||
|
}
|
||||||
|
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
|
||||||
|
* basis for its mapping. It keeps track if changes have been made since last save.
|
||||||
|
*
|
||||||
|
* @author Aleksandar Stojsavljevic
|
||||||
|
*/
|
||||||
|
final class HazelcastSession implements Session {
|
||||||
|
|
||||||
|
private final MapSession delegate;
|
||||||
|
|
||||||
|
private boolean isNew;
|
||||||
|
|
||||||
|
private boolean sessionIdChanged;
|
||||||
|
|
||||||
|
private boolean lastAccessedTimeChanged;
|
||||||
|
|
||||||
|
private boolean maxInactiveIntervalChanged;
|
||||||
|
|
||||||
|
private String originalId;
|
||||||
|
|
||||||
|
private Map<String, Object> delta = new HashMap<>();
|
||||||
|
|
||||||
|
HazelcastSession(MapSession cached, boolean isNew) {
|
||||||
|
this.delegate = cached;
|
||||||
|
this.isNew = isNew;
|
||||||
|
this.originalId = cached.getId();
|
||||||
|
if (this.isNew || (Hazelcast4IndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||||
|
getAttributeNames()
|
||||||
|
.forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||||
|
this.delegate.setLastAccessedTime(lastAccessedTime);
|
||||||
|
this.lastAccessedTimeChanged = true;
|
||||||
|
flushImmediateIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExpired() {
|
||||||
|
return this.delegate.isExpired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getCreationTime() {
|
||||||
|
return this.delegate.getCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return this.delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String changeSessionId() {
|
||||||
|
String newSessionId = this.delegate.changeSessionId();
|
||||||
|
this.sessionIdChanged = true;
|
||||||
|
return newSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getLastAccessedTime() {
|
||||||
|
return this.delegate.getLastAccessedTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxInactiveInterval(Duration interval) {
|
||||||
|
this.delegate.setMaxInactiveInterval(interval);
|
||||||
|
this.maxInactiveIntervalChanged = true;
|
||||||
|
flushImmediateIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Duration getMaxInactiveInterval() {
|
||||||
|
return this.delegate.getMaxInactiveInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T getAttribute(String attributeName) {
|
||||||
|
T attributeValue = this.delegate.getAttribute(attributeName);
|
||||||
|
if (attributeValue != null
|
||||||
|
&& Hazelcast4IndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||||
|
this.delta.put(attributeName, attributeValue);
|
||||||
|
}
|
||||||
|
return attributeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAttributeNames() {
|
||||||
|
return this.delegate.getAttributeNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String attributeName, Object attributeValue) {
|
||||||
|
this.delegate.setAttribute(attributeName, attributeValue);
|
||||||
|
this.delta.put(attributeName, attributeValue);
|
||||||
|
if (SPRING_SECURITY_CONTEXT.equals(attributeName)) {
|
||||||
|
Map<String, String> indexes = Hazelcast4IndexedSessionRepository.this.indexResolver
|
||||||
|
.resolveIndexesFor(this);
|
||||||
|
String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null;
|
||||||
|
this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||||
|
}
|
||||||
|
flushImmediateIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String attributeName) {
|
||||||
|
setAttribute(attributeName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
MapSession getDelegate() {
|
||||||
|
return this.delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasChanges() {
|
||||||
|
return (this.lastAccessedTimeChanged || this.maxInactiveIntervalChanged || !this.delta.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearChangeFlags() {
|
||||||
|
this.isNew = false;
|
||||||
|
this.lastAccessedTimeChanged = false;
|
||||||
|
this.sessionIdChanged = false;
|
||||||
|
this.maxInactiveIntervalChanged = false;
|
||||||
|
this.delta.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushImmediateIfNecessary() {
|
||||||
|
if (Hazelcast4IndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
|
||||||
|
Hazelcast4IndexedSessionRepository.this.save(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import com.hazelcast.query.extractor.ValueCollector;
|
||||||
|
import com.hazelcast.query.extractor.ValueExtractor;
|
||||||
|
|
||||||
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hazelcast {@link ValueExtractor} responsible for extracting principal name from the
|
||||||
|
* {@link MapSession} to be used with Hazelcast 4.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
public class Hazelcast4PrincipalNameExtractor implements ValueExtractor<MapSession, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void extract(MapSession target, String argument, ValueCollector collector) {
|
||||||
|
String principalName = target.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||||
|
if (principalName != null) {
|
||||||
|
collector.addObject(principalName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.hazelcast.map.EntryProcessor;
|
||||||
|
import com.hazelcast.map.ExtendedMapEntry;
|
||||||
|
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hazelcast {@link EntryProcessor} responsible for handling updates to session when using
|
||||||
|
* Hazelcast 4.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
public class Hazelcast4SessionUpdateEntryProcessor implements EntryProcessor<String, MapSession, Object> {
|
||||||
|
|
||||||
|
private Instant lastAccessedTime;
|
||||||
|
|
||||||
|
private Duration maxInactiveInterval;
|
||||||
|
|
||||||
|
private Map<String, Object> delta;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object process(Map.Entry<String, MapSession> entry) {
|
||||||
|
MapSession value = entry.getValue();
|
||||||
|
if (value == null) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
if (this.lastAccessedTime != null) {
|
||||||
|
value.setLastAccessedTime(this.lastAccessedTime);
|
||||||
|
}
|
||||||
|
if (this.maxInactiveInterval != null) {
|
||||||
|
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||||
|
}
|
||||||
|
if (this.delta != null) {
|
||||||
|
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
||||||
|
if (attribute.getValue() != null) {
|
||||||
|
value.setAttribute(attribute.getKey(), attribute.getValue());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value.removeAttribute(attribute.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.maxInactiveInterval != null) {
|
||||||
|
((ExtendedMapEntry<String, MapSession>) entry).setValue(value, this.maxInactiveInterval.getSeconds(),
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry.setValue(value);
|
||||||
|
}
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLastAccessedTime(Instant lastAccessedTime) {
|
||||||
|
this.lastAccessedTime = lastAccessedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||||
|
this.maxInactiveInterval = maxInactiveInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDelta(Map<String, Object> delta) {
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,472 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
import com.hazelcast.map.EntryProcessor;
|
||||||
|
import com.hazelcast.map.IMap;
|
||||||
|
import com.hazelcast.map.listener.MapListener;
|
||||||
|
import com.hazelcast.query.impl.predicates.EqualPredicate;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.FlushMode;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.SaveMode;
|
||||||
|
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository.HazelcastSession;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isA;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link Hazelcast4IndexedSessionRepository}.
|
||||||
|
*
|
||||||
|
* @author Eleftheria Stein
|
||||||
|
*/
|
||||||
|
class Hazelcast4IndexedSessionRepositoryTests {
|
||||||
|
|
||||||
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
|
private HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private IMap<String, MapSession> sessions = mock(IMap.class);
|
||||||
|
|
||||||
|
private Hazelcast4IndexedSessionRepository repository;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
given(this.hazelcastInstance.<String, MapSession>getMap(anyString())).willReturn(this.sessions);
|
||||||
|
this.repository = new Hazelcast4IndexedSessionRepository(this.hazelcastInstance);
|
||||||
|
this.repository.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructorNullHazelcastInstance() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> new Hazelcast4IndexedSessionRepository(null))
|
||||||
|
.withMessage("HazelcastInstance must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setSaveModeNull() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSaveMode(null))
|
||||||
|
.withMessage("saveMode must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createSessionDefaultMaxInactiveInterval() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
|
||||||
|
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createSessionCustomMaxInactiveInterval() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
int interval = 1;
|
||||||
|
this.repository.setDefaultMaxInactiveInterval(interval);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
|
||||||
|
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveNewFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveNewFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedAttributeFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setAttribute("testName", "testValue");
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedAttributeFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setAttribute("testName", "testValue");
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeAttributeFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.removeAttribute("testName");
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeAttributeFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.removeAttribute("testName");
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedLastAccessedTimeFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setLastAccessedTime(Instant.now());
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedLastAccessedTimeFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setLastAccessedTime(Instant.now());
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUpdatedMaxInactiveIntervalInSecondsFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
String sessionId = session.getId();
|
||||||
|
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||||
|
verify(this.sessions, times(1)).set(eq(sessionId), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
verify(this.sessions).setTtl(eq(sessionId), anyLong(), any());
|
||||||
|
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUnchangedFlushModeOnSave() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
this.repository.save(session);
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveUnchangedFlushModeImmediate() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
this.repository.setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
|
eq(TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSessionNotFound() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
String sessionId = "testSessionId";
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.findById(sessionId);
|
||||||
|
|
||||||
|
assertThat(session).isNull();
|
||||||
|
verify(this.sessions, times(1)).get(eq(sessionId));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSessionExpired() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
MapSession expired = new MapSession();
|
||||||
|
expired.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
|
||||||
|
given(this.sessions.get(eq(expired.getId()))).willReturn(expired);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.findById(expired.getId());
|
||||||
|
|
||||||
|
assertThat(session).isNull();
|
||||||
|
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
||||||
|
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSessionFound() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
MapSession saved = new MapSession();
|
||||||
|
saved.setAttribute("savedName", "savedValue");
|
||||||
|
given(this.sessions.get(eq(saved.getId()))).willReturn(saved);
|
||||||
|
|
||||||
|
HazelcastSession session = this.repository.findById(saved.getId());
|
||||||
|
|
||||||
|
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||||
|
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
|
||||||
|
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void delete() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
String sessionId = "testSessionId";
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
|
|
||||||
|
verify(this.sessions, times(1)).remove(eq(sessionId));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByIndexNameAndIndexValueUnknownIndexName() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
String indexValue = "testIndexValue";
|
||||||
|
|
||||||
|
Map<String, HazelcastSession> sessions = this.repository.findByIndexNameAndIndexValue("testIndexName",
|
||||||
|
indexValue);
|
||||||
|
|
||||||
|
assertThat(sessions).isEmpty();
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
String principal = "username";
|
||||||
|
|
||||||
|
Map<String, HazelcastSession> sessions = this.repository
|
||||||
|
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||||
|
|
||||||
|
assertThat(sessions).isEmpty();
|
||||||
|
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
|
||||||
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
|
String principal = "username";
|
||||||
|
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "notused",
|
||||||
|
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||||
|
List<MapSession> saved = new ArrayList<>(2);
|
||||||
|
MapSession saved1 = new MapSession();
|
||||||
|
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||||
|
saved.add(saved1);
|
||||||
|
MapSession saved2 = new MapSession();
|
||||||
|
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||||
|
saved.add(saved2);
|
||||||
|
given(this.sessions.values(isA(EqualPredicate.class))).willReturn(saved);
|
||||||
|
|
||||||
|
Map<String, HazelcastSession> sessions = this.repository
|
||||||
|
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||||
|
|
||||||
|
assertThat(sessions).hasSize(2);
|
||||||
|
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // gh-1120
|
||||||
|
void getAttributeNamesAndRemove() {
|
||||||
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
session.setAttribute("attribute1", "value1");
|
||||||
|
session.setAttribute("attribute2", "value2");
|
||||||
|
|
||||||
|
for (String attributeName : session.getAttributeNames()) {
|
||||||
|
session.removeAttribute(attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(session.getAttributeNames()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void saveWithSaveModeOnSetAttribute() {
|
||||||
|
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||||
|
MapSession delegate = new MapSession();
|
||||||
|
delegate.setAttribute("attribute1", "value1");
|
||||||
|
delegate.setAttribute("attribute2", "value2");
|
||||||
|
delegate.setAttribute("attribute3", "value3");
|
||||||
|
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||||
|
session.getAttribute("attribute2");
|
||||||
|
session.setAttribute("attribute3", "value4");
|
||||||
|
this.repository.save(session);
|
||||||
|
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||||
|
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||||
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void saveWithSaveModeOnGetAttribute() {
|
||||||
|
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||||
|
MapSession delegate = new MapSession();
|
||||||
|
delegate.setAttribute("attribute1", "value1");
|
||||||
|
delegate.setAttribute("attribute2", "value2");
|
||||||
|
delegate.setAttribute("attribute3", "value3");
|
||||||
|
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||||
|
session.getAttribute("attribute2");
|
||||||
|
session.setAttribute("attribute3", "value4");
|
||||||
|
this.repository.save(session);
|
||||||
|
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||||
|
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||||
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void saveWithSaveModeAlways() {
|
||||||
|
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||||
|
MapSession delegate = new MapSession();
|
||||||
|
delegate.setAttribute("attribute1", "value1");
|
||||||
|
delegate.setAttribute("attribute2", "value2");
|
||||||
|
delegate.setAttribute("attribute3", "value3");
|
||||||
|
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||||
|
session.getAttribute("attribute2");
|
||||||
|
session.setAttribute("attribute3", "value4");
|
||||||
|
this.repository.save(session);
|
||||||
|
ArgumentCaptor<Hazelcast4SessionUpdateEntryProcessor> captor = ArgumentCaptor
|
||||||
|
.forClass(Hazelcast4SessionUpdateEntryProcessor.class);
|
||||||
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
||||||
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,17 +1,29 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-module'
|
apply plugin: 'io.spring.convention.spring-module'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
hazelcast4
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':spring-session-core')
|
compile project(':spring-session-core')
|
||||||
compile "com.hazelcast:hazelcast"
|
compile "com.hazelcast:hazelcast"
|
||||||
compile "javax.annotation:javax.annotation-api"
|
compile "javax.annotation:javax.annotation-api"
|
||||||
compile "org.springframework:spring-context"
|
compile "org.springframework:spring-context"
|
||||||
|
|
||||||
|
hazelcast4(project(path: ":hazelcast4", configuration: 'classesOnlyElements'))
|
||||||
|
compileOnly(project(":hazelcast4"))
|
||||||
|
|
||||||
testCompile "javax.servlet:javax.servlet-api"
|
testCompile "javax.servlet:javax.servlet-api"
|
||||||
testCompile "org.springframework:spring-web"
|
testCompile "org.springframework:spring-web"
|
||||||
testCompile "org.springframework.security:spring-security-core"
|
testCompile "org.springframework.security:spring-security-core"
|
||||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||||
|
testRuntime project(':hazelcast4')
|
||||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||||
|
|
||||||
integrationTestCompile "com.hazelcast:hazelcast-client"
|
integrationTestCompile "com.hazelcast:hazelcast-client"
|
||||||
integrationTestCompile "org.testcontainers:testcontainers"
|
integrationTestCompile "org.testcontainers:testcontainers"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
from configurations.hazelcast4
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -58,16 +58,13 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
|
|||||||
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||||
.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
.getMap(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||||
|
|
||||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
|
||||||
|
|
||||||
this.repository.save(sessionToSave);
|
this.repository.save(sessionToSave);
|
||||||
|
|
||||||
assertThat(hazelcastMap.size()).isEqualTo(1);
|
|
||||||
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
assertThat(hazelcastMap.get(sessionId)).isEqualTo(sessionToSave);
|
||||||
|
|
||||||
this.repository.deleteById(sessionId);
|
this.repository.deleteById(sessionId);
|
||||||
|
|
||||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
assertThat(hazelcastMap.get(sessionId)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -183,6 +180,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
|
|||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
|
|
||||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -199,6 +198,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
|
|||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
|
|
||||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||||
|
|
||||||
|
this.repository.deleteById(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -220,6 +221,8 @@ abstract class AbstractHazelcastIndexedSessionRepositoryITests {
|
|||||||
assertThat(this.repository
|
assertThat(this.repository
|
||||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
|
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
|
||||||
.hasSize(1);
|
.hasSize(1);
|
||||||
|
|
||||||
|
this.repository.deleteById(session.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
|||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests {
|
class ClientServerHazelcastIndexedSessionRepositoryITests extends AbstractHazelcastIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.3")
|
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:3.12.12")
|
||||||
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
|
||||||
"/opt/hazelcast/hazelcast.xml");
|
"/opt/hazelcast/hazelcast.xml");
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ import com.hazelcast.config.Config;
|
|||||||
import com.hazelcast.config.MapAttributeConfig;
|
import com.hazelcast.config.MapAttributeConfig;
|
||||||
import com.hazelcast.config.MapIndexConfig;
|
import com.hazelcast.config.MapIndexConfig;
|
||||||
import com.hazelcast.config.NetworkConfig;
|
import com.hazelcast.config.NetworkConfig;
|
||||||
|
import com.hazelcast.config.SerializerConfig;
|
||||||
import com.hazelcast.core.Hazelcast;
|
import com.hazelcast.core.Hazelcast;
|
||||||
import com.hazelcast.core.HazelcastInstance;
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
|
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for Hazelcast integration tests.
|
* Utility class for Hazelcast integration tests.
|
||||||
*
|
*
|
||||||
@@ -48,6 +51,9 @@ final class HazelcastITestUtils {
|
|||||||
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
|
||||||
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
|
||||||
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
|
||||||
|
SerializerConfig serializerConfig = new SerializerConfig();
|
||||||
|
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||||
|
config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||||
return Hazelcast.newHazelcastInstance(config);
|
return Hazelcast.newHazelcastInstance(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
|
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.13.xsd">
|
||||||
|
|
||||||
<network>
|
<network>
|
||||||
<join>
|
<join>
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.hazelcast.nio.ObjectDataInput;
|
||||||
|
import com.hazelcast.nio.ObjectDataOutput;
|
||||||
|
import com.hazelcast.nio.serialization.StreamSerializer;
|
||||||
|
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link com.hazelcast.nio.serialization.Serializer} implementation that handles the
|
||||||
|
* (de)serialization of {@link MapSession} stored on {@link com.hazelcast.core.IMap}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The use of this serializer is optional and provides faster serialization of sessions.
|
||||||
|
* If not configured to be used, Hazelcast will serialize sessions via
|
||||||
|
* {@link java.io.Serializable} by default.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If multiple instances of a Spring application is run, then all of them need to use the
|
||||||
|
* same serialization method. If this serializer is registered on one instance and not
|
||||||
|
* another one, then it will end up with HazelcastSerializationException. The same applies
|
||||||
|
* when clients are configured to use this serializer but not the members, and vice versa.
|
||||||
|
* Also note that, if a new instance is created with this serialization but the existing
|
||||||
|
* Hazelcast cluster contains the values not serialized by this but instead the default
|
||||||
|
* one, this will result in incompatibility again.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An example of how to register the serializer on embedded instance can be seen below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* Config config = new Config();
|
||||||
|
*
|
||||||
|
* // ... other configurations for Hazelcast ...
|
||||||
|
*
|
||||||
|
* SerializerConfig serializerConfig = new SerializerConfig();
|
||||||
|
* serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||||
|
* config.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||||
|
*
|
||||||
|
* HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Below is the example of how to register the serializer on client instance. Note that,
|
||||||
|
* to use the serializer in client/server mode, the serializer - and hence
|
||||||
|
* {@link MapSession}, must exist on the server's classpath and must be registered via
|
||||||
|
* {@link com.hazelcast.config.SerializerConfig} with the configuration above for each
|
||||||
|
* server.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* ClientConfig clientConfig = new ClientConfig();
|
||||||
|
*
|
||||||
|
* // ... other configurations for Hazelcast Client ...
|
||||||
|
*
|
||||||
|
* SerializerConfig serializerConfig = new SerializerConfig();
|
||||||
|
* serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
|
||||||
|
* clientConfig.getSerializationConfig().addSerializerConfig(serializerConfig);
|
||||||
|
*
|
||||||
|
* HazelcastInstance hazelcastClient = HazelcastClient.newHazelcastClient(clientConfig);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Enes Ozcan
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
public class HazelcastSessionSerializer implements StreamSerializer<MapSession> {
|
||||||
|
|
||||||
|
private static final int SERIALIZER_TYPE_ID = 1453;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ObjectDataOutput out, MapSession session) throws IOException {
|
||||||
|
out.writeUTF(session.getOriginalId());
|
||||||
|
out.writeUTF(session.getId());
|
||||||
|
writeInstant(out, session.getCreationTime());
|
||||||
|
writeInstant(out, session.getLastAccessedTime());
|
||||||
|
writeDuration(out, session.getMaxInactiveInterval());
|
||||||
|
for (String attrName : session.getAttributeNames()) {
|
||||||
|
Object attrValue = session.getAttribute(attrName);
|
||||||
|
if (attrValue != null) {
|
||||||
|
out.writeUTF(attrName);
|
||||||
|
out.writeObject(attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeInstant(ObjectDataOutput out, Instant instant) throws IOException {
|
||||||
|
out.writeLong(instant.getEpochSecond());
|
||||||
|
out.writeInt(instant.getNano());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDuration(ObjectDataOutput out, Duration duration) throws IOException {
|
||||||
|
out.writeLong(duration.getSeconds());
|
||||||
|
out.writeInt(duration.getNano());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapSession read(ObjectDataInput in) throws IOException {
|
||||||
|
String originalId = in.readUTF();
|
||||||
|
MapSession cached = new MapSession(originalId);
|
||||||
|
cached.setId(in.readUTF());
|
||||||
|
cached.setCreationTime(readInstant(in));
|
||||||
|
cached.setLastAccessedTime(readInstant(in));
|
||||||
|
cached.setMaxInactiveInterval(readDuration(in));
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
// During write, it's not possible to write
|
||||||
|
// number of non-null attributes without an extra
|
||||||
|
// iteration. Hence the attributes are read until
|
||||||
|
// EOF here.
|
||||||
|
String attrName = in.readUTF();
|
||||||
|
Object attrValue = in.readObject();
|
||||||
|
cached.setAttribute(attrName, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (EOFException ignored) {
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Instant readInstant(ObjectDataInput in) throws IOException {
|
||||||
|
long seconds = in.readLong();
|
||||||
|
int nanos = in.readInt();
|
||||||
|
return Instant.ofEpochSecond(seconds, nanos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Duration readDuration(ObjectDataInput in) throws IOException {
|
||||||
|
long seconds = in.readLong();
|
||||||
|
int nanos = in.readInt();
|
||||||
|
return Duration.ofSeconds(seconds, nanos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTypeId() {
|
||||||
|
return SERIALIZER_TYPE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ import org.springframework.session.Session;
|
|||||||
import org.springframework.session.SessionRepository;
|
import org.springframework.session.SessionRepository;
|
||||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
|
||||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,11 +80,10 @@ public @interface EnableHazelcastHttpSession {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the name of the Map that will be used in Hazelcast to store the session
|
* This is the name of the Map that will be used in Hazelcast to store the session
|
||||||
* data. Default is
|
* data. Default is "spring:session:sessions".
|
||||||
* {@link HazelcastIndexedSessionRepository#DEFAULT_SESSION_MAP_NAME}.
|
|
||||||
* @return the name of the Map to store the sessions in Hazelcast
|
* @return the name of the Map to store the sessions in Hazelcast
|
||||||
*/
|
*/
|
||||||
String sessionMapName() default HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME;
|
String sessionMapName() default "spring:session:sessions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush mode for the Hazelcast sessions. The default is {@code ON_SAVE} which only
|
* Flush mode for the Hazelcast sessions. The default is {@code ON_SAVE} which only
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -35,12 +35,15 @@ import org.springframework.session.IndexResolver;
|
|||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
import org.springframework.session.SaveMode;
|
import org.springframework.session.SaveMode;
|
||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||||
|
import org.springframework.session.hazelcast.Hazelcast4IndexedSessionRepository;
|
||||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||||
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
|
||||||
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
|
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
|
||||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,23 +75,23 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
|||||||
|
|
||||||
private List<SessionRepositoryCustomizer<HazelcastIndexedSessionRepository>> sessionRepositoryCustomizers;
|
private List<SessionRepositoryCustomizer<HazelcastIndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||||
|
|
||||||
|
private List<SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository>> hazelcast4SessionRepositoryCustomizers;
|
||||||
|
|
||||||
|
private static final boolean hazelcast4;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClassLoader classLoader = HazelcastHttpSessionConfiguration.class.getClassLoader();
|
||||||
|
hazelcast4 = ClassUtils.isPresent("com.hazelcast.map.IMap", classLoader);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public HazelcastIndexedSessionRepository sessionRepository() {
|
public SessionRepository<?> sessionRepository() {
|
||||||
HazelcastIndexedSessionRepository sessionRepository = new HazelcastIndexedSessionRepository(
|
if (hazelcast4) {
|
||||||
this.hazelcastInstance);
|
return createHazelcast4IndexedSessionRepository();
|
||||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
|
||||||
if (this.indexResolver != null) {
|
|
||||||
sessionRepository.setIndexResolver(this.indexResolver);
|
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(this.sessionMapName)) {
|
else {
|
||||||
sessionRepository.setSessionMapName(this.sessionMapName);
|
return createHazelcastIndexedSessionRepository();
|
||||||
}
|
}
|
||||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
|
||||||
sessionRepository.setFlushMode(this.flushMode);
|
|
||||||
sessionRepository.setSaveMode(this.saveMode);
|
|
||||||
this.sessionRepositoryCustomizers
|
|
||||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
|
||||||
return sessionRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||||
@@ -139,6 +142,13 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
|||||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
public void setHazelcast4SessionRepositoryCustomizer(
|
||||||
|
ObjectProvider<SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||||
|
this.hazelcast4SessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||||
@@ -159,4 +169,40 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
|||||||
this.saveMode = attributes.getEnum("saveMode");
|
this.saveMode = attributes.getEnum("saveMode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HazelcastIndexedSessionRepository createHazelcastIndexedSessionRepository() {
|
||||||
|
HazelcastIndexedSessionRepository sessionRepository = new HazelcastIndexedSessionRepository(
|
||||||
|
this.hazelcastInstance);
|
||||||
|
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||||
|
if (this.indexResolver != null) {
|
||||||
|
sessionRepository.setIndexResolver(this.indexResolver);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(this.sessionMapName)) {
|
||||||
|
sessionRepository.setSessionMapName(this.sessionMapName);
|
||||||
|
}
|
||||||
|
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||||
|
sessionRepository.setFlushMode(this.flushMode);
|
||||||
|
sessionRepository.setSaveMode(this.saveMode);
|
||||||
|
this.sessionRepositoryCustomizers
|
||||||
|
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||||
|
return sessionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hazelcast4IndexedSessionRepository createHazelcast4IndexedSessionRepository() {
|
||||||
|
Hazelcast4IndexedSessionRepository sessionRepository = new Hazelcast4IndexedSessionRepository(
|
||||||
|
this.hazelcastInstance);
|
||||||
|
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||||
|
if (this.indexResolver != null) {
|
||||||
|
sessionRepository.setIndexResolver(this.indexResolver);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(this.sessionMapName)) {
|
||||||
|
sessionRepository.setSessionMapName(this.sessionMapName);
|
||||||
|
}
|
||||||
|
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||||
|
sessionRepository.setFlushMode(this.flushMode);
|
||||||
|
sessionRepository.setSaveMode(this.saveMode);
|
||||||
|
this.hazelcast4SessionRepositoryCustomizers
|
||||||
|
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||||
|
return sessionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ import static org.mockito.BDDMockito.given;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link HazelcastIndexedSessionRepository}.
|
* Tests for {@link HazelcastIndexedSessionRepository}.
|
||||||
@@ -99,7 +99,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
|
||||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
|
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,7 +112,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
|
|
||||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
|
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -120,12 +120,12 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||||
|
|
||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -137,7 +137,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -146,12 +146,12 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
session.setAttribute("testName", "testValue");
|
session.setAttribute("testName", "testValue");
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -167,7 +167,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -176,12 +176,12 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
session.removeAttribute("testName");
|
session.removeAttribute("testName");
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -197,7 +197,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -206,12 +206,12 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
session.setLastAccessedTime(Instant.now());
|
session.setLastAccessedTime(Instant.now());
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -227,7 +227,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
verify(this.sessions, times(1)).executeOnKey(eq(session.getId()), any(EntryProcessor.class));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -236,12 +236,12 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
HazelcastSession session = this.repository.createSession();
|
HazelcastSession session = this.repository.createSession();
|
||||||
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
verify(this.sessions, times(1)).set(eq(session.getId()), eq(session.getDelegate()), isA(Long.class),
|
||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -259,7 +259,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
|
verify(this.sessions, times(1)).executeOnKey(eq(sessionId), any(EntryProcessor.class));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -272,7 +272,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -286,7 +286,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
eq(TimeUnit.SECONDS));
|
eq(TimeUnit.SECONDS));
|
||||||
|
|
||||||
this.repository.save(session);
|
this.repository.save(session);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -299,7 +299,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
assertThat(session).isNull();
|
assertThat(session).isNull();
|
||||||
verify(this.sessions, times(1)).get(eq(sessionId));
|
verify(this.sessions, times(1)).get(eq(sessionId));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -315,7 +315,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
assertThat(session).isNull();
|
assertThat(session).isNull();
|
||||||
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
verify(this.sessions, times(1)).get(eq(expired.getId()));
|
||||||
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
verify(this.sessions, times(1)).remove(eq(expired.getId()));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -331,7 +331,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
assertThat(session.getId()).isEqualTo(saved.getId());
|
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||||
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
|
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
|
||||||
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
verify(this.sessions, times(1)).get(eq(saved.getId()));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -343,7 +343,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
this.repository.deleteById(sessionId);
|
this.repository.deleteById(sessionId);
|
||||||
|
|
||||||
verify(this.sessions, times(1)).remove(eq(sessionId));
|
verify(this.sessions, times(1)).remove(eq(sessionId));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -356,7 +356,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
indexValue);
|
indexValue);
|
||||||
|
|
||||||
assertThat(sessions).isEmpty();
|
assertThat(sessions).isEmpty();
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -370,7 +370,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
assertThat(sessions).isEmpty();
|
assertThat(sessions).isEmpty();
|
||||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -394,7 +394,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
assertThat(sessions).hasSize(2);
|
assertThat(sessions).hasSize(2);
|
||||||
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
verify(this.sessions, times(1)).values(isA(EqualPredicate.class));
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-1120
|
@Test // gh-1120
|
||||||
@@ -426,7 +426,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -445,7 +445,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -464,7 +464,7 @@ class HazelcastIndexedSessionRepositoryTests {
|
|||||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
||||||
verifyZeroInteractions(this.sessions);
|
verifyNoMoreInteractions(this.sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.hazelcast;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import com.hazelcast.config.SerializationConfig;
|
||||||
|
import com.hazelcast.config.SerializerConfig;
|
||||||
|
import com.hazelcast.internal.serialization.impl.DefaultSerializationServiceBuilder;
|
||||||
|
import com.hazelcast.nio.serialization.Data;
|
||||||
|
import com.hazelcast.spi.serialization.SerializationService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HazelcastSessionSerializerTests {
|
||||||
|
|
||||||
|
private SerializationService serializationService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
SerializationConfig serializationConfig = new SerializationConfig();
|
||||||
|
SerializerConfig serializerConfig = new SerializerConfig().setImplementation(new HazelcastSessionSerializer())
|
||||||
|
.setTypeClass(MapSession.class);
|
||||||
|
serializationConfig.addSerializerConfig(serializerConfig);
|
||||||
|
this.serializationService = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void serializeSessionWithStreamSerializer() {
|
||||||
|
MapSession originalSession = new MapSession();
|
||||||
|
originalSession.setAttribute("attr1", "value1");
|
||||||
|
originalSession.setAttribute("attr2", "value2");
|
||||||
|
originalSession.setAttribute("attr3", new SerializableTestAttribute(3));
|
||||||
|
originalSession.setMaxInactiveInterval(Duration.ofDays(5));
|
||||||
|
originalSession.setLastAccessedTime(Instant.now());
|
||||||
|
originalSession.setId("custom-id");
|
||||||
|
|
||||||
|
Data serialized = this.serializationService.toData(originalSession);
|
||||||
|
MapSession cached = this.serializationService.toObject(serialized);
|
||||||
|
|
||||||
|
assertThat(originalSession.getCreationTime()).isEqualTo(cached.getCreationTime());
|
||||||
|
assertThat(originalSession.getMaxInactiveInterval()).isEqualTo(cached.getMaxInactiveInterval());
|
||||||
|
assertThat(originalSession.getId()).isEqualTo(cached.getId());
|
||||||
|
assertThat(originalSession.getOriginalId()).isEqualTo(cached.getOriginalId());
|
||||||
|
assertThat(originalSession.getAttributeNames().size()).isEqualTo(cached.getAttributeNames().size());
|
||||||
|
assertThat(originalSession.<String>getAttribute("attr1")).isEqualTo(cached.getAttribute("attr1"));
|
||||||
|
assertThat(originalSession.<String>getAttribute("attr2")).isEqualTo(cached.getAttribute("attr2"));
|
||||||
|
assertThat(originalSession.<SerializableTestAttribute>getAttribute("attr3"))
|
||||||
|
.isEqualTo(cached.getAttribute("attr3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SerializableTestAttribute implements Serializable {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
SerializableTestAttribute(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof SerializableTestAttribute)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SerializableTestAttribute that = (SerializableTestAttribute) o;
|
||||||
|
return this.id == that.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ dependencies {
|
|||||||
integrationTestCompile "com.h2database:h2"
|
integrationTestCompile "com.h2database:h2"
|
||||||
integrationTestCompile "com.ibm.db2:jcc"
|
integrationTestCompile "com.ibm.db2:jcc"
|
||||||
integrationTestCompile "com.microsoft.sqlserver:mssql-jdbc"
|
integrationTestCompile "com.microsoft.sqlserver:mssql-jdbc"
|
||||||
integrationTestCompile "com.oracle.ojdbc:ojdbc8"
|
integrationTestCompile "com.oracle.database.jdbc:ojdbc8"
|
||||||
integrationTestCompile "com.zaxxer:HikariCP"
|
integrationTestCompile "com.zaxxer:HikariCP"
|
||||||
integrationTestCompile "mysql:mysql-connector-java"
|
integrationTestCompile "mysql:mysql-connector-java"
|
||||||
integrationTestCompile "org.apache.derby:derby"
|
integrationTestCompile "org.apache.derby:derby"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -29,8 +29,15 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
|
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||||
|
import org.springframework.jdbc.support.lob.LobCreator;
|
||||||
|
import org.springframework.jdbc.support.lob.LobHandler;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
@@ -38,6 +45,7 @@ import org.springframework.security.core.context.SecurityContext;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||||
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
|
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
|
||||||
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
|
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
@@ -45,6 +53,8 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
|
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
|
||||||
@@ -57,15 +67,27 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
|
|||||||
|
|
||||||
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JdbcIndexedSessionRepository repository;
|
private JdbcIndexedSessionRepository repository;
|
||||||
|
|
||||||
|
private JdbcOperations jdbcOperations;
|
||||||
|
|
||||||
|
private LobHandler lobHandler;
|
||||||
|
|
||||||
private SecurityContext context;
|
private SecurityContext context;
|
||||||
|
|
||||||
private SecurityContext changedContext;
|
private SecurityContext changedContext;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
|
this.jdbcOperations = new JdbcTemplate(this.dataSource);
|
||||||
|
this.lobHandler = new DefaultLobHandler();
|
||||||
this.context = SecurityContextHolder.createEmptyContext();
|
this.context = SecurityContextHolder.createEmptyContext();
|
||||||
this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na",
|
this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na",
|
||||||
AuthorityUtils.createAuthorityList("ROLE_USER")));
|
AuthorityUtils.createAuthorityList("ROLE_USER")));
|
||||||
@@ -564,6 +586,18 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
|
|||||||
assertThat(this.repository.findById(session.getId())).isNull();
|
assertThat(this.repository.findById(session.getId())).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void cleanupExpiredSessionsWhenMaxInactiveIntervalNegativeThenSessionNotDeleted() {
|
||||||
|
JdbcSession session = this.repository.createSession();
|
||||||
|
session.setMaxInactiveInterval(Duration.ofSeconds(-1));
|
||||||
|
session.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
|
||||||
|
|
||||||
|
this.repository.save(session);
|
||||||
|
this.repository.cleanUpExpiredSessions();
|
||||||
|
|
||||||
|
assertThat(this.repository.findById(session.getId())).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void changeSessionIdWhenOnlyChangeId() {
|
void changeSessionIdWhenOnlyChangeId() {
|
||||||
String attrName = "changeSessionId";
|
String attrName = "changeSessionId";
|
||||||
@@ -759,6 +793,32 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
|
|||||||
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
|
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // gh-1213
|
||||||
|
void saveNewSessionAttributeConcurrently() {
|
||||||
|
JdbcSession session = this.repository.createSession();
|
||||||
|
this.repository.save(session);
|
||||||
|
String attributeName = "attribute1";
|
||||||
|
String attributeValue = "value1";
|
||||||
|
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
|
||||||
|
this.jdbcOperations.update("INSERT INTO SPRING_SESSION_ATTRIBUTES VALUES (?, ?, ?)", (ps) -> {
|
||||||
|
ps.setString(1, (String) ReflectionTestUtils.getField(session, "primaryKey"));
|
||||||
|
ps.setString(2, attributeName);
|
||||||
|
lobCreator.setBlobAsBytes(ps, 3, "value2".getBytes());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
session.setAttribute(attributeName, attributeValue);
|
||||||
|
if (this.applicationContext.getBeansOfType(SessionRepositoryCustomizer.class).isEmpty()) {
|
||||||
|
// without DB specific upsert configured we're seeing duplicate key error
|
||||||
|
assertThatExceptionOfType(DuplicateKeyException.class).isThrownBy(() -> this.repository.save(session));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// with DB specific upsert configured we're fine
|
||||||
|
assertThatCode(() -> this.repository.save(session)).doesNotThrowAnyException();
|
||||||
|
assertThat((String) this.repository.findById(session.getId()).getAttribute(attributeName))
|
||||||
|
.isEqualTo(attributeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getSecurityName() {
|
private String getSecurityName() {
|
||||||
return this.context.getAuthentication().getName();
|
return this.context.getAuthentication().getName();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
package org.springframework.session.jdbc;
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
|
|
||||||
import org.testcontainers.containers.Db2Container;
|
import org.testcontainers.containers.Db2Container;
|
||||||
import org.testcontainers.containers.JdbcDatabaseContainer;
|
import org.testcontainers.containers.JdbcDatabaseContainer;
|
||||||
@@ -38,169 +37,42 @@ final class DatabaseContainers {
|
|||||||
private DatabaseContainers() {
|
private DatabaseContainers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Db211Container db211() {
|
static Db2Container db2() {
|
||||||
return new Db211Container();
|
return new Db2Container("ibmcom/db2:11.5.4.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
static MariaDBContainer mariaDb5() {
|
static MariaDBContainer<?> mariaDb() {
|
||||||
return new MariaDb5Container();
|
return new MariaDBContainer<>("mariadb:10.6.2");
|
||||||
}
|
}
|
||||||
|
|
||||||
static MariaDBContainer mariaDb10() {
|
static MySQLContainer<?> mySql() {
|
||||||
return new MariaDb10Container();
|
return new MySQLContainer<>("mysql:8.0.25");
|
||||||
}
|
}
|
||||||
|
|
||||||
static MySQLContainer mySql5() {
|
static OracleContainer oracle() {
|
||||||
return new MySql5Container();
|
return new OracleContainer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
|
||||||
|
.withStartupTimeout(Duration.ofMinutes(10));
|
||||||
|
addEnv("ORACLE_PWD", getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void waitUntilContainerStarted() {
|
||||||
|
getWaitStrategy().waitUntilReady(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static MySQLContainer mySql8() {
|
static PostgreSQLContainer<?> postgreSql() {
|
||||||
return new MySql8Container();
|
return new PostgreSQLContainer<>("postgres:13.3");
|
||||||
}
|
}
|
||||||
|
|
||||||
static OracleXeContainer oracleXe() {
|
static MSSQLServerContainer<?> sqlServer() {
|
||||||
return new OracleXeContainer();
|
return new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04");
|
||||||
}
|
|
||||||
|
|
||||||
static PostgreSQLContainer postgreSql9() {
|
|
||||||
return new PostgreSql9Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
static PostgreSQLContainer postgreSql10() {
|
|
||||||
return new PostgreSql10Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
static PostgreSQLContainer postgreSql11() {
|
|
||||||
return new PostgreSql11Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
static MSSQLServerContainer sqlServer2017() {
|
|
||||||
return new SqlServer2017Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Db211Container extends Db2Container {
|
|
||||||
|
|
||||||
Db211Container() {
|
|
||||||
super("ibmcom/db2:11.5.0.0a");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
|
|
||||||
|
|
||||||
MariaDb5Container() {
|
|
||||||
super("mariadb:5.5.64");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
super.configure();
|
|
||||||
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci",
|
|
||||||
"--innodb_large_prefix", "--innodb_file_format=barracuda", "--innodb-file-per-table");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
|
|
||||||
|
|
||||||
MariaDb10Container() {
|
|
||||||
super("mariadb:10.4.8");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
super.configure();
|
|
||||||
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MySql5Container extends MySQLContainer<MySql5Container> {
|
|
||||||
|
|
||||||
MySql5Container() {
|
|
||||||
super("mysql:5.7.27");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
super.configure();
|
|
||||||
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDriverClassName() {
|
|
||||||
return "com.mysql.cj.jdbc.Driver";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MySql8Container extends MySQLContainer<MySql8Container> {
|
|
||||||
|
|
||||||
MySql8Container() {
|
|
||||||
super("mysql:8.0.17");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
super.configure();
|
|
||||||
setCommand("mysqld", "--default-authentication-plugin=mysql_native_password");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDriverClassName() {
|
|
||||||
return "com.mysql.cj.jdbc.Driver";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class OracleXeContainer extends OracleContainer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
super.configure();
|
|
||||||
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
|
|
||||||
.withStartupTimeout(Duration.of(10, ChronoUnit.MINUTES));
|
|
||||||
setShmSize(1024L * 1024L * 1024L);
|
|
||||||
addEnv("ORACLE_PWD", getPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void waitUntilContainerStarted() {
|
|
||||||
getWaitStrategy().waitUntilReady(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PostgreSql9Container extends PostgreSQLContainer<PostgreSql9Container> {
|
|
||||||
|
|
||||||
PostgreSql9Container() {
|
|
||||||
super("postgres:9.6.15");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PostgreSql10Container extends PostgreSQLContainer<PostgreSql10Container> {
|
|
||||||
|
|
||||||
PostgreSql10Container() {
|
|
||||||
super("postgres:10.10");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PostgreSql11Container extends PostgreSQLContainer<PostgreSql11Container> {
|
|
||||||
|
|
||||||
PostgreSql11Container() {
|
|
||||||
super("postgres:11.5");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SqlServer2017Container extends MSSQLServerContainer<SqlServer2017Container> {
|
|
||||||
|
|
||||||
SqlServer2017Container() {
|
|
||||||
super("mcr.microsoft.com/mssql/server:2017-CU16");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database with
|
||||||
|
* {@link Db2JdbcIndexedSessionRepositoryCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@ContextConfiguration
|
||||||
|
class Db2JdbcIndexedSessionRepositoryCustomizerITests extends Db2JdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomizerConfig extends Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Db2JdbcIndexedSessionRepositoryCustomizer db2JdbcIndexedSessionRepositoryCustomizer() {
|
||||||
|
return new Db2JdbcIndexedSessionRepositoryCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database.
|
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database.
|
||||||
*
|
*
|
||||||
* @author Vedran Pavic
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
class Db211JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
class Db2JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config extends BaseContainerConfig {
|
static class Config extends BaseContainerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Db2Container databaseContainer() {
|
Db2Container databaseContainer() {
|
||||||
Db2Container databaseContainer = DatabaseContainers.db211();
|
Db2Container databaseContainer = DatabaseContainers.db2();
|
||||||
databaseContainer.start();
|
databaseContainer.start();
|
||||||
return databaseContainer;
|
return databaseContainer;
|
||||||
}
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.jdbc;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.testcontainers.containers.MariaDBContainer;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database.
|
|
||||||
*
|
|
||||||
* @author Vedran Pavic
|
|
||||||
*/
|
|
||||||
@ExtendWith(SpringExtension.class)
|
|
||||||
@WebAppConfiguration
|
|
||||||
@ContextConfiguration
|
|
||||||
class MariaDb10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class Config extends BaseContainerConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
MariaDBContainer databaseContainer() {
|
|
||||||
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb10();
|
|
||||||
databaseContainer.start();
|
|
||||||
return databaseContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ResourceDatabasePopulator databasePopulator() {
|
|
||||||
return DatabasePopulators.mySql();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database with
|
||||||
|
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@ContextConfiguration
|
||||||
|
class MariaDbJdbcIndexedSessionRepositoryCustomizerITests extends MariaDbJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomizerConfig extends Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
|
||||||
|
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 5.x database.
|
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database.
|
||||||
*
|
*
|
||||||
* @author Vedran Pavic
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
class MariaDb5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
class MariaDbJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config extends BaseContainerConfig {
|
static class Config extends BaseContainerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MariaDBContainer databaseContainer() {
|
MariaDBContainer<?> databaseContainer() {
|
||||||
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb5();
|
MariaDBContainer<?> databaseContainer = DatabaseContainers.mariaDb();
|
||||||
databaseContainer.start();
|
databaseContainer.start();
|
||||||
return databaseContainer;
|
return databaseContainer;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database with
|
||||||
|
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@ContextConfiguration
|
||||||
|
class MySqlJdbcIndexedSessionRepositoryCustomizerITests extends MySqlJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomizerConfig extends Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
|
||||||
|
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database.
|
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database.
|
||||||
*
|
*
|
||||||
* @author Vedran Pavic
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
class MySql8JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
class MySqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config extends BaseContainerConfig {
|
static class Config extends BaseContainerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MySQLContainer databaseContainer() {
|
MySQLContainer<?> databaseContainer() {
|
||||||
MySQLContainer databaseContainer = DatabaseContainers.mySql8();
|
MySQLContainer<?> databaseContainer = DatabaseContainers.mySql();
|
||||||
databaseContainer.start();
|
databaseContainer.start();
|
||||||
return databaseContainer;
|
return databaseContainer;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database with
|
||||||
|
* {@link OracleJdbcIndexedSessionRepositoryCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@ContextConfiguration
|
||||||
|
class OracleJdbcIndexedSessionRepositoryCustomizerITests extends OracleJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomizerConfig extends Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
OracleJdbcIndexedSessionRepositoryCustomizer oracleJdbcIndexedSessionRepositoryCustomizer() {
|
||||||
|
return new OracleJdbcIndexedSessionRepositoryCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -54,7 +54,7 @@ class OracleJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcInde
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
OracleContainer databaseContainer() {
|
OracleContainer databaseContainer() {
|
||||||
OracleContainer databaseContainer = DatabaseContainers.oracleXe();
|
OracleContainer databaseContainer = DatabaseContainers.oracle();
|
||||||
databaseContainer.start();
|
databaseContainer.start();
|
||||||
return databaseContainer;
|
return databaseContainer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.jdbc;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 10.x
|
|
||||||
* database.
|
|
||||||
*
|
|
||||||
* @author Vedran Pavic
|
|
||||||
*/
|
|
||||||
@ExtendWith(SpringExtension.class)
|
|
||||||
@WebAppConfiguration
|
|
||||||
@ContextConfiguration
|
|
||||||
class PostgreSql10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class Config extends BaseContainerConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PostgreSQLContainer databaseContainer() {
|
|
||||||
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql10();
|
|
||||||
databaseContainer.start();
|
|
||||||
return databaseContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ResourceDatabasePopulator databasePopulator() {
|
|
||||||
return DatabasePopulators.postgreSql();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.session.jdbc;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x
|
|
||||||
* database.
|
|
||||||
*
|
|
||||||
* @author Vedran Pavic
|
|
||||||
*/
|
|
||||||
@ExtendWith(SpringExtension.class)
|
|
||||||
@WebAppConfiguration
|
|
||||||
@ContextConfiguration
|
|
||||||
class PostgreSql11JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class Config extends BaseContainerConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PostgreSQLContainer databaseContainer() {
|
|
||||||
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql11();
|
|
||||||
databaseContainer.start();
|
|
||||||
return databaseContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ResourceDatabasePopulator databasePopulator() {
|
|
||||||
return DatabasePopulators.postgreSql();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.session.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database
|
||||||
|
* with {@link PostgreSqlJdbcIndexedSessionRepositoryCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@ContextConfiguration
|
||||||
|
class PostgreSqlJdbcIndexedSessionRepositoryCustomizerITests extends PostgreSqlJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomizerConfig extends Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() {
|
||||||
|
return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -27,22 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 9.x
|
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database.
|
||||||
* database.
|
|
||||||
*
|
*
|
||||||
* @author Vedran Pavic
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
class PostgreSql9JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
class PostgreSqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config extends BaseContainerConfig {
|
static class Config extends BaseContainerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
PostgreSQLContainer databaseContainer() {
|
PostgreSQLContainer<?> databaseContainer() {
|
||||||
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql9();
|
PostgreSQLContainer<?> databaseContainer = DatabaseContainers.postgreSql();
|
||||||
databaseContainer.start();
|
databaseContainer.start();
|
||||||
return databaseContainer;
|
return databaseContainer;
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user