Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
478160e5b2 | ||
|
|
2ef91fa7a2 | ||
|
|
8cd185db6b | ||
|
|
6fbd11e893 | ||
|
|
401bff267c | ||
|
|
7805aff531 | ||
|
|
7d5ba54289 | ||
|
|
45e6c3a0ba | ||
|
|
9f142ed49d | ||
|
|
0758dd737d | ||
|
|
f7852f379a | ||
|
|
61d3d56e6d | ||
|
|
7d929457e4 | ||
|
|
3d266960ba | ||
|
|
4d96099e03 | ||
|
|
e0103f62d6 | ||
|
|
304b496caf | ||
|
|
c1e3c2831d | ||
|
|
e228279683 | ||
|
|
70c14368e5 | ||
|
|
7f9abc822f | ||
|
|
f426f91574 | ||
|
|
316fe09f72 | ||
|
|
00338e23dd | ||
|
|
c6c2d53204 | ||
|
|
bf5dcda905 | ||
|
|
32ec8b2b28 | ||
|
|
3268d1f790 | ||
|
|
380a1e81ac | ||
|
|
5be4141103 | ||
|
|
4ebf18dc4e | ||
|
|
b5c67736ad | ||
|
|
dfab409f30 | ||
|
|
a0a394d17f | ||
|
|
1a98f25fdb | ||
|
|
1afb5d5a17 | ||
|
|
365a244a9b | ||
|
|
0b4140d892 | ||
|
|
78a85789c9 | ||
|
|
59350ed559 | ||
|
|
811e156a9c | ||
|
|
05a9903348 | ||
|
|
d8ae336b24 | ||
|
|
315112f2a2 | ||
|
|
e859da6d27 | ||
|
|
028bae1f11 | ||
|
|
234cb6dd88 | ||
|
|
43101308ec | ||
|
|
089f6b92de | ||
|
|
c6d129a5a5 |
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
[NOTE]
|
||||
======
|
||||
This branch of Spring Session has reached its https://github.com/spring-projects/spring-boot/wiki/Supported-Versions[End of Life], meaning that there are no further maintenance releases or security patches planned.
|
||||
Please migrate to a supported branch as soon as possible.
|
||||
======
|
||||
|
||||
= Spring Session
|
||||
|
||||
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
|
||||
|
||||
20
build.gradle
20
build.gradle
@@ -4,7 +4,7 @@ buildscript {
|
||||
snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||
|
||||
springBootVersion = '2.2.5.RELEASE'
|
||||
springBootVersion = '2.2.13.RELEASE'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -13,7 +13,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.28.RELEASE'
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.27.1.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
}
|
||||
}
|
||||
@@ -34,3 +34,19 @@ subprojects {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty('artifactoryUsername')) {
|
||||
allprojects { project ->
|
||||
project.repositories { repos ->
|
||||
all { repo ->
|
||||
if (!repo.url.toString().startsWith("https://repo.spring.io/")) {
|
||||
return;
|
||||
}
|
||||
repo.credentials {
|
||||
username = artifactoryUsername
|
||||
password = artifactoryPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=true
|
||||
version=2.3.0.RC1
|
||||
version=2.2.7.BUILD-SNAPSHOT
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-SR5'
|
||||
mavenBom 'org.junit:junit-bom:5.6.0'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.2.4.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Neumann-M3'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.3.0.RC1'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.12.2'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-SR19'
|
||||
mavenBom 'org.junit:junit-bom:5.5.2'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.2.14.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-SR13'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.2.10.RELEASE'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.15.3'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.6') {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.12') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
@@ -18,18 +18,18 @@ dependencyManagement {
|
||||
dependency 'com.ibm.db2:jcc:11.5.0.0'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.4.1.jre8'
|
||||
dependency 'com.oracle.ojdbc:ojdbc8:19.3.0.0'
|
||||
dependency 'com.zaxxer:HikariCP:3.4.1'
|
||||
dependency 'com.zaxxer:HikariCP:3.4.5'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.2.2.RELEASE'
|
||||
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
dependency 'javax.servlet:javax.servlet-api:4.0.1'
|
||||
dependency 'junit:junit:4.13'
|
||||
dependency 'mysql:mysql-connector-java:8.0.19'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'mysql:mysql-connector-java:8.0.23'
|
||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||
dependency 'org.assertj:assertj-core:3.15.0'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.0'
|
||||
dependency 'org.assertj:assertj-core:3.13.2'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.1'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.4'
|
||||
dependency 'org.mockito:mockito-core:3.3.0'
|
||||
dependency 'org.postgresql:postgresql:42.2.10'
|
||||
dependency 'org.mockito:mockito-core:3.0.0'
|
||||
dependency 'org.postgresql:postgresql:42.2.16'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.springframework.session.events.SessionDestroyedEvent;
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public MapSessionRepository sessionRepository() {
|
||||
* return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
* return new MapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public MapSessionRepository sessionRepository() {
|
||||
* return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
* return new MapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.context.annotation.Import;
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public ReactiveSessionRepository sessionRepository() {
|
||||
* return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
|
||||
* return new ReactiveMapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
|
||||
@@ -309,6 +309,10 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
if (!create) {
|
||||
return null;
|
||||
}
|
||||
if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
|
||||
&& this.response.isCommitted()) {
|
||||
throw new IllegalStateException("Cannot create a session after the response has been committed");
|
||||
}
|
||||
if (SESSION_LOGGER.isDebugEnabled()) {
|
||||
SESSION_LOGGER.debug(
|
||||
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -423,6 +424,18 @@ class SessionRepositoryFilterTests {
|
||||
assertThat(this.response.getCookie("SESSION")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterGetSessionNewWhenResponseCommittedThenException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> doFilter(new DoInFilter() {
|
||||
@Override
|
||||
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse)
|
||||
throws IOException {
|
||||
wrappedResponse.getWriter().flush();
|
||||
wrappedRequest.getSession();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterGetSessionNew() throws Exception {
|
||||
doFilter(new DoInFilter() {
|
||||
|
||||
@@ -142,7 +142,7 @@ import org.springframework.util.Assert;
|
||||
* <p>
|
||||
* 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
|
||||
* "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the session id. The body of the event will be
|
||||
* "33fdd1b6-b496-4b33-9f7d-df96679d32fe" is the sesion id. The body of the event will be
|
||||
* the session that was created.
|
||||
* </p>
|
||||
*
|
||||
|
||||
@@ -46,7 +46,11 @@ asciidoctor {
|
||||
'spring-session-version': project.version,
|
||||
'version-milestone': milestoneBuild,
|
||||
'version-release': releaseBuild,
|
||||
'version-snapshot': snapshotBuild,
|
||||
'highlightjsdir@': "js/highlight",
|
||||
'docinfodir@': "."
|
||||
'version-snapshot': snapshotBuild
|
||||
}
|
||||
|
||||
remotes {
|
||||
docs {
|
||||
host = "docs-ip.spring.io"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
= Spring Session - find by username
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
[[findbyusername-assumptions]]
|
||||
== Assumptions
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - Spring Boot
|
||||
Rob Winch, Vedran Pavić
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - Spring Boot
|
||||
Rob Winch, Vedran Pavić
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before you use Spring Session, you must ensure your dependencies.
|
||||
@@ -157,6 +151,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
|
||||
----
|
||||
====
|
||||
=====
|
||||
|
||||
Now you can visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
= Spring Session - WebSocket
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:toc:
|
||||
: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.
|
||||
|
||||
@@ -15,9 +12,6 @@ Specifically,it does not work with using https://www.jcp.org/en/jsr/detail?id=35
|
||||
|
||||
// end::disclaimer[]
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== 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].
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<script type="text/javascript" src="../js/tocbot/tocbot.min.js"></script>
|
||||
<script type="text/javascript" src="../js/toc.js"></script>
|
||||
@@ -1,18 +1,12 @@
|
||||
= Spring Session - Custom Cookie
|
||||
Rob Winch; Eleftheria Stein-Kousathana
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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].
|
||||
|
||||
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]]
|
||||
== Spring Java Configuration
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
= Spring Session and Spring Security with Hazelcast
|
||||
Tommy Ludwig; Rob Winch
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - HttpSession (Quick Start)
|
||||
Rob Winch, Vedran Pavić
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
@@ -105,7 +99,7 @@ For additional information on how to configure data access related concerns, see
|
||||
|
||||
== Java Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-jdbc-spring-configuration,Spring Configuration>> created a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
Our <<httpsession-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.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
= Spring Session - HttpSession (Quick Start)
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:toc:
|
||||
: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.
|
||||
|
||||
NOTE: You can find the completed guide in the <<httpsession-sample, httpsession sample application>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
If you are using Maven, you must add the following dependencies:
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - REST
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
@@ -247,7 +241,7 @@ $ curl -v http://localhost:8080/ -u user:password
|
||||
|
||||
In the output, you should notice the following:
|
||||
|
||||
====
|
||||
===
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
= Spring Session and Spring Security
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session along with Spring Security.
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
If you use Maven, you must add the following dependencies:
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - HttpSession (Quick Start)
|
||||
Rob Winch, Vedran Pavić
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
@@ -74,8 +68,8 @@ You must have the following in your pom.xml:
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
====
|
||||
endif::[]
|
||||
====
|
||||
|
||||
// tag::config[]
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
= Spring Session - HttpSession (Quick Start)
|
||||
Rob Winch
|
||||
:toc: left
|
||||
:stylesdir: ../
|
||||
:highlightjsdir: ../js/highlight
|
||||
:docinfodir: guides
|
||||
:toc:
|
||||
|
||||
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>>.
|
||||
|
||||
[#index-link]
|
||||
link:../index.html[Index]
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must update your dependencies.
|
||||
If you use Maven, you must add the following dependencies:
|
||||
|
||||
@@ -19,7 +19,6 @@ package docs.security;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
@@ -42,16 +41,14 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ... additional configuration ...
|
||||
.rememberMe((rememberMe) -> rememberMe
|
||||
.rememberMeServices(rememberMeServices())
|
||||
);
|
||||
.rememberMe()
|
||||
.rememberMeServices(rememberMeServices());
|
||||
// end::http-rememberme[]
|
||||
|
||||
http
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
.formLogin().and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
}
|
||||
|
||||
// tag::rememberme-bean[]
|
||||
|
||||
@@ -40,10 +40,9 @@ public class SecurityConfiguration<S extends Session> extends WebSecurityConfigu
|
||||
// @formatter:off
|
||||
http
|
||||
// other config goes here...
|
||||
.sessionManagement((sessionManagement) -> sessionManagement
|
||||
.sessionManagement()
|
||||
.maximumSessions(2)
|
||||
.sessionRegistry(sessionRegistry())
|
||||
);
|
||||
.sessionRegistry(sessionRegistry());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@WebAppConfiguration
|
||||
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"),
|
||||
"/opt/hazelcast/hazelcast.xml");
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ final class DatabaseContainers {
|
||||
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
|
||||
|
||||
MariaDb10Container() {
|
||||
super("mariadb:10.4.8");
|
||||
super("mariadb:10.4.18");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -103,7 +103,7 @@ import org.springframework.util.StringUtils;
|
||||
* );
|
||||
*
|
||||
* CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
|
||||
* CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
|
||||
* CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
|
||||
*
|
||||
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.10.0'
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.10.5.20201202'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -9,15 +9,15 @@ dependencyManagement {
|
||||
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2'
|
||||
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.0'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.28'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.28'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
|
||||
dependency 'org.webjars:bootstrap:2.3.2'
|
||||
dependency 'org.webjars:html5shiv:3.7.3'
|
||||
dependency 'org.webjars:jquery:1.12.4'
|
||||
dependency 'org.webjars:knockout:2.3.0'
|
||||
dependency 'org.webjars:sockjs-client:0.3.4'
|
||||
dependency 'org.webjars:stomp-websocket:2.3.0'
|
||||
dependency 'org.webjars:stomp-websocket:2.3.4'
|
||||
dependency 'org.webjars:webjars-taglib:0.3'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ class FindByUsernameTests {
|
||||
|
||||
private WebDriver driver;
|
||||
|
||||
private WebDriver driver2;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
||||
@@ -61,6 +63,9 @@ class FindByUsernameTests {
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
this.driver.quit();
|
||||
if (this.driver2 != null) {
|
||||
this.driver2.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,6 +84,25 @@ class FindByUsernameTests {
|
||||
home.terminateButtonDisabled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void terminateOtherSession() throws Exception {
|
||||
HomePage forgotToLogout = home(this.driver);
|
||||
|
||||
this.driver2 = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
||||
HomePage terminateFogotSession = home(this.driver2);
|
||||
terminateFogotSession.terminateSession(forgotToLogout.getSessionId()).assertAt();
|
||||
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
login.assertAt();
|
||||
}
|
||||
|
||||
private static HomePage home(WebDriver driver) {
|
||||
LoginPage login = HomePage.go(driver);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
home.assertAt();
|
||||
return home;
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class Config {
|
||||
|
||||
|
||||
@@ -56,6 +56,18 @@ public class HomePage extends BasePage {
|
||||
}
|
||||
|
||||
public void terminateButtonDisabled() {
|
||||
String sessionId = getSessionId();
|
||||
WebElement element = getDriver().findElement(By.id("terminate-" + sessionId));
|
||||
assertThat(element.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
public HomePage terminateSession(String sessionId) {
|
||||
WebElement terminate = getDriver().findElement(By.id("terminate-" + sessionId));
|
||||
terminate.click();
|
||||
return new HomePage(getDriver());
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
String cookieValue = null;
|
||||
for (Cookie cookie : cookies) {
|
||||
@@ -63,8 +75,7 @@ public class HomePage extends BasePage {
|
||||
cookieValue = new String(Base64.getDecoder().decode(cookie.getValue()));
|
||||
}
|
||||
}
|
||||
WebElement element = getDriver().findElement(By.id("terminate-" + cookieValue));
|
||||
assertThat(element.isEnabled()).isFalse();
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
public HomePage logout() {
|
||||
|
||||
@@ -35,14 +35,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.permitAll()
|
||||
);
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
@@ -26,8 +26,8 @@ import org.springframework.session.Session;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Controller for sending the user to the login view.
|
||||
@@ -50,7 +50,7 @@ public class IndexController {
|
||||
}
|
||||
// end::findbyusername[]
|
||||
|
||||
@RequestMapping(value = "/sessions/{sessionIdToDelete}", method = RequestMethod.DELETE)
|
||||
@PostMapping("/sessions/{sessionIdToDelete}")
|
||||
public String removeSession(Principal principal, @PathVariable String sessionIdToDelete) {
|
||||
Set<String> usersSessionIds = this.sessions.findByPrincipalName(principal.getName()).keySet();
|
||||
if (usersSessionIds.contains(sessionIdToDelete)) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<td th:text="${#temporals.format(sessionElement.lastAccessedTime.atZone(T(java.time.ZoneId).systemDefault()),'dd/MMM/yyyy HH:mm:ss')}"></td>
|
||||
<td th:text="${details?.accessType}"></td>
|
||||
<td>
|
||||
<form th:action="@{'/sessions/' + ${sessionElement.id}}" th:method="delete">
|
||||
<form th:action="@{'/sessions/' + ${sessionElement.id}}" th:method="post">
|
||||
<input th:id="'terminate-' + ${sessionElement.id}" type="submit" value="Terminate" th:disabled="${sessionElement.id == #httpSession.id}"/>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
@@ -44,13 +44,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.permitAll()
|
||||
);
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
@@ -34,14 +34,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.permitAll()
|
||||
);
|
||||
.permitAll();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -28,13 +28,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.permitAll()
|
||||
);
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -35,13 +35,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.permitAll()
|
||||
);
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// end::config[]
|
||||
// @formatter:on
|
||||
|
||||
@@ -53,13 +53,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin((formLogin) -> formLogin
|
||||
.permitAll()
|
||||
);
|
||||
.and()
|
||||
.formLogin()
|
||||
.permitAll();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -37,4 +37,4 @@ public class SessionServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 2878267318695777395L;
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
// tag::end[]
|
||||
|
||||
@@ -38,4 +38,4 @@ public class SessionServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 2878267318695777395L;
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
// tag::end[]
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package sample;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -32,13 +31,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.requestCache((requestCache) -> requestCache
|
||||
.and()
|
||||
.requestCache()
|
||||
.requestCache(new NullRequestCache())
|
||||
)
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
.and()
|
||||
.httpBasic();
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
Reference in New Issue
Block a user