Compare commits

..

50 Commits

Author SHA1 Message Date
Rob Winch
676f0e474e Release 2.0.0.RC2 2017-11-27 21:53:36 -06:00
Rob Winch
e5ec612771 Update to Spring Security 5.0.0.RELEASE
Fixes gh-926
2017-11-27 21:53:00 -06:00
Vedran Pavic
280d5c5a77 Refactor JDBC configuration
Closes gh-942
2017-11-27 22:21:40 +01:00
Vedran Pavic
6a370b1ef8 Refactor Redis configuration
Closes gh-941
2017-11-27 22:21:40 +01:00
Vedran Pavic
41de1b087a Refactor Hazelcast configuration
Closes gh-938
2017-11-27 22:21:40 +01:00
Vedran Pavic
6188fe68b7 Improve session event handling
This commit removes constructor that takes session id instead of session object for the entire `AbstractSessionEvent` hierarchy.

The ability to create `AbstractSessionEvent` instances with no underlying session object leads to NPE when interacting with `HttpSession` obtained from `HttpSessionEvent`.

See gh-499
Closes gh-939
2017-11-27 22:21:40 +01:00
Rob Winch
ed328ff4b1 spring-build-conventions:0.0.8.RELEASE 2017-11-27 14:35:15 -06:00
Vedran Pavic
97ad0311e2 Upgrade Spring Data to Kay-SR2
Closes gh-932
2017-11-27 20:07:03 +01:00
Vedran Pavic
702bc37a99 Upgrade Spring Framework to 5.0.2.RELEASE
Closes gh-925
2017-11-27 12:56:09 +01:00
Vedran Pavic
17e56dda18 Polish configuration classes 2017-11-26 12:21:32 +01:00
Vedran Pavic
f5912da089 Optimize HazelcastSessionRepository write operations
This commit introduces several optimizations to write operations in `HazelcastSessionRepository`.

 - when storing a new session, `IMap#set` is now used instead of `IMap#put`
 - when updating an existing session, `IMap#executeOnKey` and a dedicated `EntryProcessor` are used

To make these two changes possible, internal `HazelcastSession` now adds a flag to determine which of the two mentioned write scenarios to use, and also tracks a delta of session attributes in order to optimize updates.

Closes gh-850
2017-11-24 21:06:05 +01:00
Vedran Pavic
bff8ce3c03 Polish samples 2017-11-24 08:21:14 +01:00
Vedran Pavic
a3803e9e1f Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2017-11-23 13:41:48 +01:00
Vedran Pavic
3fcdc9ebce Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-11-23 13:15:47 +01:00
Vedran Pavic
36d157a658 Polish default Redis namespace handling
See gh-919
2017-11-20 09:12:53 +01:00
Vedran Pavic
f28ab07b9a Migrate SpringJUnit4ClassRunner -> SpringRunner 2017-11-20 08:08:00 +01:00
Vedran Pavic
42a6001aae Upgrade Reactor to Bismuth-SR4
Closes gh-929
2017-11-16 20:01:04 +01:00
Vedran Pavic
fc4d2238bc Rename MapReactiveSessionRepository to ReactiveMapSessionRepository
Closes gh-928
2017-11-14 07:26:13 +01:00
Vedran Pavic
36d349f328 Polish contribution
Closes gh-919
2017-11-13 20:59:39 +01:00
Luís Duarte
5f23a41674 Make Redis namespace fully configurable
See gh-919
2017-11-10 22:23:25 +01:00
Vedran Pavic
4c9fbd5b6b Migrate WebFlux sample to Boot
Closes gh-923
2017-11-10 22:11:54 +01:00
Vedran Pavic
f2ba773ec2 Upgrade Spring Boot to 2.0.0.M6
Closes gh-916
2017-11-06 13:01:22 +01:00
Vedran Pavic
647dd7c7bb Add license file 2017-11-02 19:27:25 +01:00
Rob Winch
555223755d Next Development Version 2017-10-30 18:22:30 -05:00
Rob Winch
2e65d89ecc Release 2.0.0.RC1 2017-10-30 18:20:38 -05:00
Rob Winch
f3f18432ee Update to Spring Security 5.0.0.RC1
Fixes gh-904
2017-10-30 18:17:39 -05:00
Vedran Pavic
03f6611e04 Update integration tests
This commit updates TestContainers dependency and versions of Docker images used in integration tests.
2017-10-30 09:05:36 +01:00
Vedran Pavic
fff1d83097 Upgrade dependencies
This commit harmonizes project dependencies with Spring IO Platform Cairo levels.
2017-10-30 09:01:58 +01:00
Vedran Pavic
91d4a5bfca Add HeaderHttpSessionIdResolver factory methods for commonly used headers
Closes gh-706
2017-10-30 08:07:07 +01:00
Vedran Pavic
34f29cf36c Improve Hazelcast configuration
This commit improves Hazelcast configuration by introducing `@SpringSessionHazelcastInstance` qualifier for explicitly declaring a `HazelcastInstance` to be used by Spring Session. This is in particular useful in scenarios with multiple `HazelcastInstance` beans present in the application context.

 Closes gh-912
2017-10-30 08:03:02 +01:00
Vedran Pavic
7e26897ec2 Add support for configuring Redis session cleanup cron 2017-10-30 01:33:36 -05:00
Vedran Pavic
9ea1fb9af1 Upgrade Spring Data to Kay-SR1
Closes gh-903
2017-10-27 18:31:20 +02:00
Vedran Pavic
2c664d1d9e Move JDBC qualifier annotations to shared package
Closes gh-909
2017-10-27 18:29:49 +02:00
Vedran Pavic
97698fd590 Add support for configuring JDBC session cleanup cron 2017-10-27 14:55:28 +02:00
Vedran Pavic
fe3f40c6f4 Harmonize Redis configurations
This commit improves reactive Redis configuration by adding support for connection factory qualifier and Redis operations resolver annotations.
2017-10-27 13:54:25 +02:00
Vedran Pavic
f8583bb02f Add missing @Override 2017-10-27 09:59:55 +02:00
Vedran Pavic
5df555cd53 Polish 2017-10-27 09:59:51 +02:00
Vedran Pavic
6f05c84aa7 Rename HttpSessionStrategy to HttpSessionIdResolver
This commit harmonizes `HttpSessionStrategy` with Spring Framework's `WebSessionIdResolver` by renaming it to `WebSessionIdResolver`.
2017-10-26 07:29:56 -05:00
Vedran Pavic
cd394bbe10 Align HttpSessionStrategy with WebSessionIdResolver
This commit simplifies `HttpSessionStrategy` API by aligning it with Spring Framework's `WebSessionIdResolver`. As a part of this, support for managing multiple users' sessions has been removed.

Closes gh-275
Closes gh-362
2017-10-26 07:29:56 -05:00
Vedran Pavic
2ecb2e60c0 Improve Redis configuration
This commit improves Redis configuration by introducing `@SpringSessionRedisConnectionFactory` qualifier for explicitly declaring a `RedisConnectionFactory` to be used by Spring Session. This is in particular useful in scenarios with multiple `RedisConnectionFactory` beans present in the application context.

Redis configuration is simplified and no longer registers a Spring Session specific `RedisOperations<Object,Object>` bean with the application context.

Users are however able to obtain `RedisOperations<Object,Object>` instance used by Spring Session using newly introduced `@SpringSessionRedisOperations` annotation.
2017-10-25 07:36:44 -05:00
Vedran Pavic
d04a95ebfb Upgrade Spring Framework to 5.0.1.RELEASE
Closes gh-902
2017-10-24 19:25:07 +02:00
Vedran Pavic
858b52235e Upgrade Reactor to Bismuth-SR3
Closes gh-905
2017-10-24 19:24:29 +02:00
Vedran Pavic
00ede81665 Harmonize naming of reactive components
Closes gh-897
2017-10-24 07:36:25 +02:00
Rob Winch
6cfa975b29 Move Redis Reactive Configuration
Renaming the package to better align with WebSession pacakge structure

Fixes gh-901
2017-10-22 22:37:21 -05:00
Rob Winch
8b9d421ad6 Tangles in RedisOperationsSessionRepository
Issue: gh-900
2017-10-22 22:33:36 -05:00
Rob Winch
df7ab9d99e Package tangles for Reactive Configuration
Fixes gh-900
2017-10-22 22:33:29 -05:00
Vedran Pavic
7d61c5496a Fix deprecation warnings 2017-10-20 21:18:31 +02:00
Vedran Pavic
3492bc01d2 Upgrade Spring Boot to 2.0.0.M5
Closes gh-892
2017-10-12 11:35:46 +02:00
Vedran Pavic
e08ac357dd Upgrade spring-build-conventions to 0.0.5.RELEASE 2017-10-10 22:06:01 +02:00
Rob Winch
1c29c7f14f Update to 5.0.0.BUILD-SNAPSHOT 2017-10-09 17:10:11 -05:00
189 changed files with 3315 additions and 4831 deletions

202
LICENSE.txt Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

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

View File

@@ -1,162 +0,0 @@
= Spring Session - Multiple Sessions
Rob Winch
:toc:
This guide describes how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
== Integrating with Spring Session
The steps to integrate with Spring Session are exactly the same as those outline in the link:httpsession.html[HttpSession Guide], so we will skip to running the sample application.
[[users-sample]]
== users Sample Application
The users application demonstrates how to allow an application to manage multiple simultaneous browser sessions (i.e. Google Accounts).
=== Running the users Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must http://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.
====
----
$ ./gradlew :samples:users:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the users Sample Application
Try using the application. Authenticate with the following information:
* **Username** _rob_
* **Password** _rob_
Now click the **Login** button. You should now be authenticated as the user **rob**.
We can click on links and our user information is preserved.
* Click on the **Link** link in the navigation bar at the top
* Observe we are still authenticated as **rob**
Let's add an another account.
* Return to the *Home* page
* Click on the arrow next to *rob* in the upper right hand corner
* Click **Add Account**
The log in form is displayed again. Authenticate with the following information:
* **Username** _luke_
* **Password** _luke_
Now click the **Login** button. You should now be authenticated as the user **luke**.
We can click on links and our user information is preserved.
* Click on the **Link** link in the navigation bar at the top
* Observe we are still authenticated as **luke**
Where did our original user go? Let's switch to our original account.
* Click on the arrow next to *luke* in the upper right hand corner.
* Click on **Switch Account** -> *rob*
We are now using the session associated with *rob*.
== How does it work?
// tag::how-does-it-work[]
Let's take a look at how Spring Session keeps track of multiple sessions.
=== Managing a Single Session
Spring Session keeps track of the `HttpSession` by adding a value to a cookie named SESSION.
For example, the SESSION cookie might have a value of:
7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
=== Adding a Session
We can add another session by requesting a URL that contains a special parameter in it.
By default the parameter name is *_s*. For example, the following URL would create a new session:
http://localhost:8080/?_s=1
NOTE: The parameter value does not indicate the actual session id.
This is important because we never want to allow the session id to be determined by a client to avoid https://www.owasp.org/index.php/Session_fixation[session fixation attacks].
Additionally, we do not want the session id to be leaked since it is sent as a query parameter.
Remember sensitive information should only be transmitted as a header or in the body of the request.
Rather than creating the URL ourselves, we can utilize the `HttpSessionManager` to do this for us.
We can obtain the `HttpSessionManager` from the `HttpServletRequest` using the following:
.src/main/java/sample/UserAccountsFilter.java
[source,java,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=HttpSessionManager]
----
We can now use it to create a URL to add another session.
.src/main/java/sample/UserAccountsFilter.java
[source,java,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=addAccountUrl]
----
<1> We have an existing variable named `unauthenticatedAlias`.
The value is an alias that points to an existing unauthenticated session.
If no such session exists, the value is null.
This ensures if we have an existing unauthenticated session that we use it instead of creating a new session.
<2> If all of our sessions are already associated to a user, we create a new session alias.
<3> If there is an existing session that is not associated to a user, we use its session alias.
<4> Finally, we create the add account URL.
The URL contains a session alias that either points to an existing unauthenticated session or is an alias that is unused thus signaling to create a new session associated to that alias.
Now our SESSION cookie looks something like this:
0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad
Such that:
* There is a session with the id *7e8383a4-082c-4ffe-a4bc-c40fd3363c5e*
** The alias for this session is *0*.
For example, if the URL is http://localhost:8080/?_s=0 this alias would be used.
** This is the default session.
This means that if no session alias is specified, then this session is used.
For example, if the URL is http://localhost:8080/ this session would be used.
* There is a session with the id *1d526d4a-c462-45a4-93d9-84a39b6d44ad*
** The alias for this session is *1*.
If the session alias is *1*, then this session is used.
For example, if the URL is http://localhost:8080/?_s=1 this alias would be used.
=== Automatic Session Alias Inclusion with encodeURL
The nice thing about specifying the session alias in the URL is that we can have multiple tabs open with different active sessions.
The bad thing is that we need to include the session alias in every URL of our application.
Fortunately, Spring Session will automatically include the session alias in any URL that passes through http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#encodeURL(java.lang.String)[HttpServletResponse#encodeURL(java.lang.String)]
This means that if you are using standard tag libraries the session alias is automatically included in the URL.
For example, if we are currently using the session with the alias of *1*, then the following:
.src/main/webapp/index.jsp
[source,xml,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/webapp/index.jsp[tags=link]
----
will output a link of:
[source,html]
----
<a id="navLink" href="/link.jsp?_s=1">Link</a>
----
// end::how-does-it-work[]

View File

@@ -18,7 +18,6 @@ Spring Session provides an API and implementations for managing a user's session
* <<httpsession,HttpSession>> - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way.
Additional features include:
** **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
** **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
** **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
* <<websocket,WebSocket>> - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
@@ -98,10 +97,6 @@ If you are looking to get started with Spring Session, the best place to start i
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
| link:guides/java-rest.html[REST Guide]
| {gh-samples-url}javaconfig/users[Multiple Users]
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
| link:guides/java-users.html[Multiple Users Guide]
|===
.Sample Applications using Spring XML based configuration
@@ -144,7 +139,6 @@ This means that developers can switch the `HttpSession` implementation out with
We have already mentioned that Spring Session provides transparent integration with `HttpSession`, but what benefits do we get out of this?
* **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
* **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
* **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
[[httpsession-redis]]
@@ -284,17 +278,6 @@ public class SessionRepositoryFilter implements Filter {
By passing in a custom `HttpServletRequest` implementation into the `FilterChain` we ensure that anything invoked after our `Filter` uses the custom `HttpSession` implementation.
This highlights why it is important that Spring Session's `SessionRepositoryFilter` must be placed before anything that interacts with the `HttpSession`.
[[httpsession-multi]]
=== Multiple HttpSessions in Single Browser
Spring Session has the ability to support multiple sessions in a single browser instance.
This provides the ability to support authenticating with multiple users in the same browser instance (i.e. Google Accounts).
NOTE: The <<samples,Manage Multiple Users Guide>> provides a complete working example of managing multiple users in the same browser instance.
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed Manage Multiple Users Guide when integrating with your own application.
include::guides/java-users.adoc[tags=how-does-it-work,leveloffset=+1]
[[httpsession-rest]]
=== HttpSession & RESTful APIs
@@ -562,7 +545,7 @@ Complete example usage can be found in the <<samples>>
You can use the following attributes to customize the configuration:
* **maxInactiveIntervalInSeconds** - the amount of time before the session will expire in seconds
* **redisNamespace** - allows configuring an application specific namespace for the sessions. Redis keys and channel ids will start with the prefix of `spring:session:<redisNamespace>:`.
* **redisNamespace** - allows configuring an application specific namespace for the sessions. Redis keys and channel ids will start with the prefix of `<redisNamespace>:`.
* **redisFlushMode** - allows specifying when data will be written to Redis. The default is only when `save` is invoked on `SessionRepository`.
A value of `RedisFlushMode.IMMEDIATE` will write to Redis as soon as possible.

View File

@@ -24,7 +24,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.Session;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests {

View File

@@ -22,10 +22,9 @@ import java.util.concurrent.ConcurrentHashMap;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import org.junit.Test;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
@@ -112,9 +111,12 @@ public class IndexDocTests {
@SuppressWarnings("unused")
public void newRedisOperationsSessionRepository() {
// tag::new-redisoperationssessionrepository[]
LettuceConnectionFactory factory = new LettuceConnectionFactory();
SessionRepository<? extends Session> repository = new RedisOperationsSessionRepository(
factory);
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository =
new RedisOperationsSessionRepository(redisTemplate);
// end::new-redisoperationssessionrepository[]
}
@@ -155,11 +157,8 @@ public class IndexDocTests {
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
IMap<String, MapSession> sessions = hazelcastInstance
.getMap("spring:session:sessions");
HazelcastSessionRepository repository =
new HazelcastSessionRepository(sessions);
new HazelcastSessionRepository(hazelcastInstance);
// end::new-hazelcastsessionrepository[]
}

View File

@@ -25,7 +25,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.mockito.Mockito.mock;
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {

View File

@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,7 +42,7 @@ import static org.mockito.Mockito.mock;
* @author Mark Paluch
* @since 1.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@WebAppConfiguration
public abstract class AbstractHttpSessionListenerTests {
@Autowired
@@ -82,6 +82,7 @@ public abstract class AbstractHttpSessionListenerTests {
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
* springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(SessionDestroyedEvent event) {
this.event = event;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public class HazelcastHttpSessionConfig {
Config config = new Config();
config.getMapConfig("spring:session:sessions") // <2>
config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
.addMapAttributeConfig(attributeConfig)
.addMapIndexConfig(new MapIndexConfig(
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));

View File

@@ -67,10 +67,8 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
@Override
@Bean
public InMemoryUserDetailsManager userDetailsService() {
InMemoryUserDetailsManager uds = new InMemoryUserDetailsManager();
uds.createUser(
User.withUsername("user").password("password").roles("USER").build());
return uds;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
@Bean

View File

@@ -30,7 +30,7 @@ import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -44,7 +44,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
@WebAppConfiguration
@SuppressWarnings("rawtypes")

View File

@@ -30,7 +30,7 @@ import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -44,7 +44,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
/**
* @author rwinch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("rawtypes")

View File

@@ -32,6 +32,7 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages").withSockJS();
}

View File

@@ -20,7 +20,7 @@
<security:user-service>
<security:user name="user" password="password" authorities="ROLE_USER"/>
<security:user name="user" password="{noop}password" authorities="ROLE_USER"/>
</security:user-service>
<bean class="org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"/>

View File

@@ -4,7 +4,6 @@
<suppressions>
<suppress files=".+Application\.java" checks="HideUtilityClassConstructor"/>
<suppress files=".+Configuration\.java" checks="HideUtilityClassConstructor"/>
<suppress files=".+SSEFluxWebConfig\.java" checks="RegexpHeader"/>
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc"/>
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc"/>

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.0.M4
version=2.0.0.M5
springBootVersion=2.0.0.M6
version=2.0.0.RC2

View File

@@ -1,55 +1,37 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.1'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-RELEASE'
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-RELEASE'
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M5'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.2'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR4'
mavenBom 'org.springframework:spring-framework-bom:5.0.2.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR2'
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.RELEASE'
}
dependencies {
dependencySet(group: 'com.hazelcast', version: '3.8.6') {
dependencySet(group: 'com.hazelcast', version: '3.9') {
entry 'hazelcast'
entry 'hazelcast-client'
}
dependencySet(group: 'org.testcontainers', version: '1.4.2') {
dependencySet(group: 'org.testcontainers', version: '1.4.3') {
entry 'mariadb'
entry 'mysql'
entry 'postgresql'
entry 'testcontainers'
}
dependency 'ch.qos.logback:logback-classic:1.2.3'
dependency 'com.h2database:h2:1.4.196'
dependency 'com.maxmind.geoip2:geoip2:2.3.1'
dependency 'commons-codec:commons-codec:1.10'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:5.0.0.RELEASE'
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'io.lettuce:lettuce-core:5.0.1.RELEASE'
dependency 'javax.servlet:javax.servlet-api:3.1.0'
dependency 'junit:junit:4.12'
dependency 'mysql:mysql-connector-java:5.1.44'
dependency 'org.apache.derby:derby:10.13.1.1'
dependency 'org.apache.httpcomponents:httpclient:4.5.3'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.apache.derby:derby:10.14.1.0'
dependency 'org.assertj:assertj-core:3.8.0'
dependency 'org.hsqldb:hsqldb:2.4.0'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
dependency 'org.mockito:mockito-core:2.10.0'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.0'
dependency 'org.mockito:mockito-core:2.12.0'
dependency 'org.postgresql:postgresql:42.1.4'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.27'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
dependency 'org.testcontainers:mariadb:1.3.0'
dependency 'org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.1.RELEASE'
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.7.RC1'
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:webjars-taglib:0.3'
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.28.1'
}
}

View File

@@ -20,9 +20,3 @@ dependencies {
integrationTestCompile seleniumDependencies
integrationTestCompile "org.testcontainers:testcontainers"
}
integrationTest {
doFirst {
systemProperties['spring.session.redis.namespace'] = project.name
}
}

View File

@@ -35,11 +35,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withUsername("user").password("password").roles("USER").build());
return manager;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:off

View File

@@ -57,6 +57,7 @@ public class SessionDetailsFilter extends OncePerRequestFilter {
}
// tag::dofilterinternal[]
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);

View File

@@ -20,12 +20,11 @@ import com.maxmind.geoip2.DatabaseReader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import sample.config.GeoConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Rob Winch
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = GeoConfig.class)
public class SessionDetailsFilterTests {
@Autowired

View File

@@ -36,11 +36,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withUsername("user").password("password").roles("USER").build());
return manager;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:off

View File

@@ -21,13 +21,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.testcontainers.containers.GenericContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@@ -48,7 +48,7 @@ public class RedisSerializerTest {
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
.withExposedPorts(6379);
@Autowired
@SpringSessionRedisOperations
private RedisTemplate<Object, Object> sessionRedisTemplate;
@Test

View File

@@ -35,11 +35,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withUsername("user").password("password").roles("USER").build());
return manager;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:off

View File

@@ -56,6 +56,7 @@ public class SessionConfig implements BeanClassLoaderAware {
* org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang
* .ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.loader = classLoader;
}

View File

@@ -35,11 +35,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withUsername("user").password("password").roles("USER").build());
return manager;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:off

View File

@@ -0,0 +1,15 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session-data-redis')
compile "org.springframework.boot:spring-boot-starter-webflux"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-data-redis"
compile "org.springframework.boot:spring-boot-devtools"
compile 'org.webjars:bootstrap'
testCompile "org.springframework.boot:spring-boot-starter-test"
integrationTestCompile seleniumDependencies
integrationTestCompile "org.testcontainers:testcontainers"
}

View File

@@ -16,21 +16,26 @@
package sample;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.testcontainers.containers.GenericContainer;
import sample.pages.HomePage;
import sample.pages.HomePage.Attribute;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,11 +45,18 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Rob Winch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxApplication.class)
@TestPropertySource(properties = { "spring.profiles.active=embedded-redis", "server.port=0" })
@SpringBootTest(classes = HelloWebFluxApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = AttributeTests.Initializer.class)
public class AttributeTests {
@Value("#{@nettyContext.address().getPort()}")
int port;
private static final String DOCKER_IMAGE = "redis:4.0.2";
@ClassRule
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
.withExposedPorts(6379);
@LocalServerPort
private int port;
private WebDriver driver;
@@ -87,4 +99,18 @@ public class AttributeTests {
assertThat(row.getAttributeValue()).isEqualTo("b");
}
static class Initializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(
ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues
.of("spring.redis.host=" + redisContainer.getContainerIpAddress(),
"spring.redis.port=" + redisContainer.getFirstMappedPort())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}

View File

@@ -14,22 +14,19 @@
* limitations under the License.
*/
package sample.pages;
package sample;
import org.openqa.selenium.WebDriver;
import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Pool Dolorier
* @author Rob Winch
*/
public class LinkPage extends BasePage {
@SpringBootApplication
public class HelloWebFluxApplication {
public LinkPage(WebDriver driver) {
super(driver);
public static void main(String[] args) {
SpringApplication.run(HelloWebFluxApplication.class, args);
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Linked Page");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -35,11 +35,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withUsername("user").password("password").roles("USER").build());
return manager;
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:off

View File

@@ -31,10 +31,12 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
public class WebSocketConfig
extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { // <1>
@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) { // <2>
registry.addEndpoint("/messages").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,8 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import org.springframework.security.crypto.password.PasswordEncoder;

View File

@@ -49,6 +49,7 @@ public class UserRepositoryUserDetailsService implements UserDetailsService {
* org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername
* (java.lang.String)
*/
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = this.userRepository.findByEmail(username);
@@ -64,26 +65,32 @@ public class UserRepositoryUserDetailsService implements UserDetailsService {
super(user);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
@Override
public String getUsername() {
return getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}

View File

@@ -41,6 +41,7 @@ public class WebSocketConnectHandler<S>
this.repository = repository;
}
@Override
public void onApplicationEvent(SessionConnectEvent event) {
MessageHeaders headers = event.getMessage().getHeaders();
Principal user = SimpMessageHeaderAccessor.getUser(headers);

View File

@@ -36,6 +36,7 @@ public class WebSocketDisconnectHandler<S>
this.repository = repository;
}
@Override
public void onApplicationEvent(SessionDisconnectEvent event) {
String id = event.getSessionId();
if (id == null) {

View File

@@ -0,0 +1,18 @@
dependencyManagement {
dependencies {
dependency 'ch.qos.logback:logback-classic:1.2.3'
dependency 'com.maxmind.geoip2:geoip2:2.3.1'
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
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:webjars-taglib:0.3'
}
}

View File

@@ -36,10 +36,12 @@ import com.hazelcast.nio.serialization.StreamSerializer;
*
*/
public class ObjectStreamSerializer implements StreamSerializer<Object> {
@Override
public int getTypeId() {
return 2;
}
@Override
public void write(ObjectDataOutput objectDataOutput, Object object)
throws IOException {
ObjectOutputStream out = new ObjectOutputStream((OutputStream) objectDataOutput);
@@ -47,6 +49,7 @@ public class ObjectStreamSerializer implements StreamSerializer<Object> {
out.flush();
}
@Override
public Object read(ObjectDataInput objectDataInput) throws IOException {
ObjectInputStream in = new ObjectInputStream((InputStream) objectDataInput);
try {
@@ -57,6 +60,7 @@ public class ObjectStreamSerializer implements StreamSerializer<Object> {
}
}
@Override
public void destroy() {
}

View File

@@ -16,18 +16,19 @@
package sample;
/**
* @author Rob Winch
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public class SessionConfig {
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractor(PrincipalNameExtractor.class.getName());
config.getMapConfig("spring:session:sessions")
config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME)
.addMapAttributeConfig(attributeConfig)
.addMapIndexConfig(new MapIndexConfig(
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));

View File

@@ -24,6 +24,7 @@ import org.springframework.web.WebApplicationInitializer;
public class H2ConsoleInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addServlet("h2Console", new WebServlet()).addMapping("/h2-console/*");
}

View File

@@ -31,11 +31,11 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.session.Session;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.HeaderHttpSessionStrategy;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { RestMockMvcTests.Config.class, SecurityConfig.class,
MvcConfig.class })
@WebAppConfiguration
@@ -104,8 +104,8 @@ public class RestMockMvcTests {
}
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
}

View File

@@ -21,8 +21,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.HeaderHttpSessionStrategy;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
@Import(EmbeddedRedisConfig.class)
// tag::class[]
@@ -36,8 +36,8 @@ public class HttpSessionConfig {
}
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy(); // <3>
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken(); // <3>
}
}
// end::class[]

View File

@@ -21,6 +21,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
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;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.savedrequest.NullRequestCache;
@EnableWebSecurity
@@ -40,12 +41,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
// @formatter:on
// @formatter:off
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
// @formatter:on
}

View File

@@ -16,18 +16,19 @@
package sample;
/**
* @author Rob Winch
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder()
.username("user").password("password").roles("USER").build());
}
}

View File

@@ -1,5 +0,0 @@
dependencyManagement {
dependencies {
dependency 'org.webjars:bootstrap:3.3.6'
}
}

View File

@@ -1,24 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"
compile "org.webjars:webjars-taglib"
compile jstlDependencies
compile slf4jDependencies
compile "org.testcontainers:testcontainers"
providedCompile "javax.servlet:javax.servlet-api"
testCompile "junit:junit"
testCompile "org.springframework:spring-test"
testCompile "org.assertj:assertj-core"
integrationTestCompile seleniumDependencies
}
gretty {
jvmArgs = ['-Dspring.profiles.active=embedded-redis']
}

View File

@@ -1,236 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import sample.pages.HomePage;
import sample.pages.LinkPage;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class UserTests {
private static final String ROB = "rob";
private static final String USERNAME = "username";
private static final String LUKE = "luke";
private static final String NAV_LINK = "navLink";
private static final String HREF = "href";
private static final String ADD_ACCOUNT = "addAccount";
private static final String UN = "un";
private static final String LOGOUT = "logout";
private static final String ERROR = "error";
private WebDriver driver;
@Before
public void setup() {
this.driver = new HtmlUnitDriver();
}
@After
public void tearDown() {
this.driver.quit();
}
@Test
public void firstVisitNotAuthenticated() {
HomePage homePage = HomePage.go(this.driver);
homePage.assertAt();
homePage.assertUserNameEmpty();
}
@Test
public void invalidLogin() {
HomePage homePage = HomePage.go(this.driver);
String user = ROB;
homePage.login(user, user + "invalid");
WebElement errorMessage = homePage.getElementById(ERROR);
homePage.assertAt();
homePage.assertErrorInvalidAuthentication(errorMessage);
}
@Test
public void emptyUsername() {
HomePage homePage = HomePage.go(this.driver);
homePage.login("", "");
WebElement errorMessage = homePage.getElementById(ERROR);
homePage.assertAt();
homePage.assertErrorInvalidAuthentication(errorMessage);
}
@Test
public void loginSingleUser() {
loginRob();
}
@Test
public void addAccount() {
backHomeForAddLukeAccount();
}
@Test
public void logInSecondUser() {
logInLukeAccount();
}
@Test
public void followingLinksKeepsNewSession() {
followingLukeLinkSession();
}
@Test
public void switchAccountRob() {
switchAccountRobHomePage();
}
@Test
public void followingLinksKeepsOriginalSession() {
followingRobLinkSession();
}
@Test
public void switchAccountLuke() {
switchAccountLukeHomePage();
}
@Test
public void logoutLuke() {
logoutLukeAccount();
}
@Test
public void switchBackRob() {
switchBackRobHomePage();
}
@Test
public void logoutRob() {
logoutRobAccount();
}
private HomePage loginRob() {
HomePage home = HomePage.go(this.driver);
String user = ROB;
home.login(user, user);
WebElement username = home.getElementById(UN);
assertThat(username.getText()).isEqualTo(user);
return home;
}
private HomePage backHomeForAddLukeAccount() {
HomePage robHome = loginRob();
String addAccountLink = robHome.getContentAttributeByElementId(ADD_ACCOUNT, HREF);
HomePage backHome = robHome.home(this.driver, addAccountLink);
WebElement username = backHome.getElementById(USERNAME);
assertThat(username.getText()).isEmpty();
return backHome;
}
private HomePage logInLukeAccount() {
HomePage home = backHomeForAddLukeAccount();
String secondUser = LUKE;
home.login(secondUser, secondUser);
WebElement secondUserName = home.getElementById(UN);
assertThat(secondUserName.getText()).isEqualTo(secondUser);
return home;
}
private LinkPage followingLukeLinkSession() {
HomePage lukeHome = logInLukeAccount();
String navLink = lukeHome.getContentAttributeByElementId(NAV_LINK, HREF);
LinkPage lukeLinkPage = lukeHome.linkPage(this.driver, navLink);
lukeLinkPage.assertAt();
WebElement username = lukeLinkPage.getElementById(UN);
assertThat(username.getText()).isEqualTo(LUKE);
return lukeLinkPage;
}
private HomePage switchAccountRobHomePage() {
LinkPage lukeLinkPage = followingLukeLinkSession();
String robSwitch = lukeLinkPage.getSwitchElementId(ROB);
String switchLink = lukeLinkPage.getContentAttributeByElementId(robSwitch, HREF);
HomePage robHome = lukeLinkPage.home(this.driver, switchLink);
WebElement username = robHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robHome;
}
private LinkPage followingRobLinkSession() {
HomePage robHome = switchAccountRobHomePage();
String navLink = robHome.getContentAttributeByElementId(NAV_LINK, HREF);
LinkPage robLinkPage = robHome.linkPage(this.driver, navLink);
robLinkPage.assertAt();
WebElement username = robLinkPage.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robLinkPage;
}
private HomePage switchAccountLukeHomePage() {
LinkPage robLinkPage = followingRobLinkSession();
String lukeSwitch = robLinkPage.getSwitchElementId(LUKE);
String lukeSwitchLink = robLinkPage.getContentAttributeByElementId(lukeSwitch, HREF);
HomePage lukeHome = robLinkPage.home(this.driver, lukeSwitchLink);
WebElement username = lukeHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(LUKE);
return lukeHome;
}
private HomePage logoutLukeAccount() {
HomePage lukeHome = switchAccountLukeHomePage();
String logoutLink = lukeHome.getContentAttributeByElementId(LOGOUT, HREF);
HomePage home = lukeHome.home(this.driver, logoutLink);
home.assertUserNameEmpty();
return home;
}
private HomePage switchBackRobHomePage() {
HomePage homePage = logoutLukeAccount();
String robSwitch = homePage.getSwitchElementId(ROB);
String robSwitchLink = homePage.getContentAttributeByElementId(robSwitch, HREF);
HomePage robHome = homePage.home(this.driver, robSwitchLink);
WebElement username = robHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robHome;
}
private HomePage logoutRobAccount() {
HomePage robHome = switchBackRobHomePage();
String logoutLink = robHome.getContentAttributeByElementId(LOGOUT, HREF);
HomePage home = robHome.home(this.driver, logoutLink);
home.assertUserNameEmpty();
return home;
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public abstract class BasePage {
private WebDriver driver;
public BasePage(WebDriver driver) {
this.driver = driver;
}
public WebDriver getDriver() {
return this.driver;
}
public static void get(WebDriver driver, String get) {
String baseUrl = "http://localhost:" + System.getProperty("app.port");
driver.get(baseUrl + get);
}
public static void getFrom(WebDriver driver, String resourceUrl) {
driver.get(resourceUrl);
}
public HomePage home(WebDriver driver, String resourceUrl) {
getFrom(driver, resourceUrl);
return PageFactory.initElements(driver, HomePage.class);
}
public LinkPage linkPage(WebDriver driver, String resourceUrl) {
getFrom(driver, resourceUrl);
return PageFactory.initElements(driver, LinkPage.class);
}
public String getSwitchElementId(String user) {
return "switchAccount" + user;
}
public WebElement getElementById(String id) {
return this.driver.findElement(By.id(id));
}
public String getContentAttributeByElementId(String id, String attribute) {
WebElement element = getElementById(id);
assertThat(element.getAttribute(attribute)).isNotEmpty();
return element.getAttribute(attribute);
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class HomePage extends BasePage {
@FindBy(name = "username")
private WebElement username;
@FindBy(name = "password")
private WebElement password;
@FindBy(css = "form[method='post']")
private WebElement form;
public HomePage(WebDriver driver) {
super(driver);
}
public static HomePage go(WebDriver driver) {
get(driver, "/");
return PageFactory.initElements(driver, HomePage.class);
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Demonstrates Multi User Log In");
}
public void assertUserNameEmpty() {
assertThat(this.username.getText()).isEmpty();
}
public void assertErrorInvalidAuthentication(WebElement errorMessage) {
assertThat(errorMessage.getText()).isEqualTo("Invalid username / password. Please ensure the username is the same as the password.");
}
public void login(String user, String password) {
this.username.sendKeys(user);
this.password.sendKeys(password);
this.form.submit();
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
public class Account {
private String username;
private String logoutUrl;
private String switchAccountUrl;
public Account(String username, String logoutUrl, String switchAccountUrl) {
super();
this.username = username;
this.logoutUrl = logoutUrl;
this.switchAccountUrl = switchAccountUrl;
}
public String getUsername() {
return this.username;
}
public String getLogoutUrl() {
return this.logoutUrl;
}
public String getSwitchAccountUrl() {
return this.switchAccountUrl;
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
* @author Rob Winch
*/
@Import(EmbeddedRedisConfig.class)
// tag::class[]
@Configuration
@EnableRedisHttpSession
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
// end::class[]

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import org.testcontainers.containers.GenericContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
@Bean(initMethod = "start")
public GenericContainer redisContainer() {
return new GenericContainer(REDIS_DOCKER_IMAGE) {
@Override
public void close() {
super.close();
try {
this.dockerClient.close();
}
catch (IOException ignored) {
}
}
}.withExposedPorts(6379);
}
@Bean
@Primary
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisContainer().getContainerIpAddress(),
redisContainer().getFirstMappedPort());
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import javax.servlet.ServletContext;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
/**
* @author Rob Winch
*/
public class Initializer extends AbstractHttpSessionApplicationInitializer {
public Initializer() {
super(Config.class);
}
@Override
protected void afterSessionRepositoryFilter(ServletContext servletContext) {
appendFilters(servletContext, new UserAccountsFilter());
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username != null && !"".equals(username) && username.equals(password)) {
req.getSession().setAttribute("username", username);
String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url);
}
else {
String url = resp.encodeRedirectURL(req.getContextPath() + "/?error");
resp.sendRedirect(url);
}
}
private static final long serialVersionUID = -8157634860354132501L;
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session != null) {
session.invalidate();
}
String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url);
}
private static final long serialVersionUID = 4061762524521437433L;
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.HttpSessionManager;
public class UserAccountsFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
@SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// tag::HttpSessionManager[]
HttpSessionManager sessionManager = (HttpSessionManager) httpRequest
.getAttribute(HttpSessionManager.class.getName());
// end::HttpSessionManager[]
SessionRepository<Session> repo = (SessionRepository<Session>) httpRequest
.getAttribute(SessionRepository.class.getName());
String currentSessionAlias = sessionManager.getCurrentSessionAlias(httpRequest);
Map<String, String> sessionIds = sessionManager.getSessionIds(httpRequest);
String unauthenticatedAlias = null;
String contextPath = httpRequest.getContextPath();
List<Account> accounts = new ArrayList<>();
Account currentAccount = null;
for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String sessionId = entry.getValue();
Session session = repo.findById(sessionId);
if (session == null) {
continue;
}
String username = session.getAttribute("username");
if (username == null) {
unauthenticatedAlias = alias;
continue;
}
String logoutUrl = sessionManager.encodeURL("./logout", alias);
String switchAccountUrl = sessionManager.encodeURL("./", alias);
Account account = new Account(username, logoutUrl, switchAccountUrl);
if (currentSessionAlias.equals(alias)) {
currentAccount = account;
}
else {
accounts.add(account);
}
}
// tag::addAccountUrl[]
String addAlias = unauthenticatedAlias == null ? // <1>
sessionManager.getNewSessionAlias(httpRequest)
: // <2>
unauthenticatedAlias; // <3>
String addAccountUrl = sessionManager.encodeURL(contextPath, addAlias); // <4>
// end::addAccountUrl[]
httpRequest.setAttribute("currentAccount", currentAccount);
httpRequest.setAttribute("addAccountUrl", addAccountUrl);
httpRequest.setAttribute("accounts", accounts);
chain.doFilter(request, response);
}
public void destroy() {
}
}

View File

@@ -1,14 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.security" level="DEBUG"/> -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -1,22 +0,0 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014 Twitter, Inc.
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see http://creativecommons.org/licenses/by/3.0/.
*/
// See the Getting Started docs for more information:
// http://getbootstrap.com/getting-started/#support-ie10-width
(function () {
'use strict';
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
var msViewportStyle = document.createElement('style')
msViewportStyle.appendChild(
document.createTextNode(
'@-ms-viewport{width:auto!important}'
)
)
document.querySelector('head').appendChild(msViewportStyle)
}
})();

View File

@@ -1,112 +0,0 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demonstrates Multi User Log In</title>
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
<style type="text/css">
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<!-- Static navbar -->
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="https://github.com/spring-projects/spring-session/">Spring Session</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<c:url value="/" var="homeUrl"/>
<li class="active"><a id="navHome" href="${homeUrl}">Home</a></li>
<li>
<!-- tag::link[]
-->
<c:url value="/link.jsp" var="linkUrl"/>
<a id="navLink" href="${linkUrl}">Link</a>
<!-- end::link[]
-->
</li>
</ul>
<c:if test="${currentAccount != null or not empty accounts}">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="toggle" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><c:out value="${username}"/> <span class="caret"></span></a>
<ul id="user-menu" class="dropdown-menu" role="menu">
<c:if test="${currentAccount != null}">
<li><a id="logout" href="${currentAccount.logoutUrl}">Log Out</a></li>
<li><a id="addAccount" href="${addAccountUrl}">Add Account</a></li>
</c:if>
<c:if test="${not empty accounts}">
<li class="divider"></li>
<li class="dropdown-header">Switch Account</li>
<li class="divider"></li>
</c:if>
<c:forEach items="${accounts}" var="account">
<c:set var="encodedUsername">
<c:out value="${account.username}"/>
</c:set>
<li><a id="switchAccount${encodedUsername}" href="${account.switchAccountUrl}"><c:out value="${account.username}"/></a></li>
</c:forEach>
</ul>
</li>
</ul>
</c:if>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
<h1>Description</h1>
<p>This application demonstrates how to use Spring Session to authenticate as multiple users at a time. View authenticated users in the upper right corner.</p>
<c:choose>
<c:when test="${username == null}">
<h1>Please Log In</h1>
<p>You are not currently authenticated with this session. You can authenticate with any username password combination that are equal. A few examples to try:</p>
<ul>
<li><b>Username</b> rob and <b>Password</b> rob</li>
<li><b>Username</b> luke and <b>Password</b> luke</li>
</ul>
<c:if test="${param.error != null}">
<div id="error" class="alert alert-danger">Invalid username / password. Please ensure the username is the same as the password.</div>
</c:if>
<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post">
<div class="form-group">
<label for="username">Username</label>
<input id="username" type="text" name="username"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" type="password" name="password"/>
</div>
<input type="submit" value="Login"/>
</form>
</c:when>
<c:otherwise>
<h1 id="un"><c:out value="${username}"/></h1>
<p>You are authenticated as <b><c:out value="${username}"/></b>. Observe that you can <a href="${linkUrl}">navigate links</a> and the correct session is used. Using the links in the upper right corner you can:</p>
<ul>
<li>Log Out</li>
<li>Switch Accounts</li>
<li>Add Account</li>
</ul>
</c:otherwise>
</c:choose>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<wj:locate path="jquery.min.js" relativeTo="META-INF/resources" var="jqueryLocation"/>
<script src="<c:url value="${jqueryLocation}"/>"></script>
<wj:locate path="bootstrap.min.js" relativeTo="META-INF/resources" var="bootstrapJsLocation"/>
<script src="<c:url value="${bootstrapJsLocation}"/>"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="<c:url value="/assets/js/ie10-viewport-bug-workaround.js"/>"></script>
</body>
</html>

View File

@@ -1,85 +0,0 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Linked Page</title>
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
<style type="text/css">
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<!-- Static navbar -->
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="https://github.com/spring-projects/spring-session/">Spring Session</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<c:url value="/" var="homeUrl"/>
<li><a id="navHome" href="${homeUrl}">Home</a></li>
<c:url value="/link.jsp" var="linkUrl"/>
<li class="active"><a id="navLink" href="${linkUrl}">Link</a></li>
</ul>
<c:if test="${currentAccount != null or not empty accounts}">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="toggle" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><c:out value="${username}"/> <span class="caret"></span></a>
<ul id="user-menu" class="dropdown-menu" role="menu">
<c:if test="${currentAccount != null}">
<li><a id="logout" href="${currentAccount.logoutUrl}">Log Out</a></li>
<li><a id="addAccount" href="${addAccountUrl}">Add Account</a></li>
</c:if>
<c:if test="${not empty accounts}">
<li class="divider"></li>
<li class="dropdown-header">Switch Account</li>
<li class="divider"></li>
</c:if>
<c:forEach items="${accounts}" var="account">
<li><a id="switchAccount${account.username}" href="${account.switchAccountUrl}"><c:out value="${account.username}"/></a></li>
</c:forEach>
</ul>
</li>
</ul>
</c:if>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
<h1>Description</h1>
<p>This page demonstrates how we keep track of the correct user session between links even when multiple tabs are open. Try opening another tab with a different user and browse. Then come back to the original tab and see the correct user is maintained.</p>
<c:choose>
<c:when test="${username == null}">
<h1>Please Log In</h1>
<p>You are not currently authenticated with this session. <a href="${homeUrl}">Log In</a></p>
</c:when>
<c:otherwise>
<h1 id="un"><c:out value="${username}"/></h1>
<p>You are authenticated as <b><c:out value="${username}"/></b>. Observe that you can <a href="${linkUrl}">navigate links</a> and the correct session is used. Using the links in the upper right corner you can:</p>
<ul>
<li>Log Out</li>
<li>Switch Accounts</li>
<li>Add Account</li>
</ul>
</c:otherwise>
</c:choose>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<wj:locate path="jquery.min.js" relativeTo="META-INF/resources" var="jqueryLocation"/>
<script src="<c:url value="${jqueryLocation}"/>"></script>
<wj:locate path="bootstrap.min.js" relativeTo="META-INF/resources" var="bootstrapJsLocation"/>
<script src="<c:url value="${bootstrapJsLocation}"/>"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="<c:url value="/assets/js/ie10-viewport-bug-workaround.js"/>"></script>
</body>
</html>

View File

@@ -1,23 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample'
dependencies {
compile project(':spring-session-data-redis')
compile 'io.lettuce:lettuce-core'
compile 'io.netty:netty-buffer'
compile 'io.projectreactor.ipc:reactor-netty'
compile 'org.springframework:spring-context'
compile 'org.springframework:spring-web'
compile 'org.springframework:spring-webflux'
compile 'org.thymeleaf.extras:thymeleaf-extras-java8time'
compile 'org.thymeleaf:thymeleaf-spring5'
compile 'org.webjars:bootstrap'
compile 'org.webjars:webjars-taglib'
compile jstlDependencies
compile slf4jDependencies
compile 'org.testcontainers:testcontainers'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
integrationTestCompile seleniumDependencies
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.io.IOException;
import org.testcontainers.containers.GenericContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
@Bean(initMethod = "start")
public GenericContainer redisContainer() {
return new GenericContainer(REDIS_DOCKER_IMAGE) {
@Override
public void close() {
super.close();
try {
this.dockerClient.close();
}
catch (IOException ignored) {
}
}
}.withExposedPorts(6379);
}
@Bean
@Primary
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisContainer().getContainerIpAddress(),
redisContainer().getFirstMappedPort());
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
/**
* @author Rob Winch
* @since 5.0
*/
@Configuration
@EnableWebFlux
@ComponentScan
public class HelloWebfluxApplication {
@Value("${server.port:8080}")
private int port = 8080;
public static void main(String[] args) throws Exception {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
HelloWebfluxApplication.class)) {
context.getBean(NettyContext.class).onClose().block();
}
}
@Bean
public NettyContext nettyContext(ApplicationContext context) {
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer httpServer = HttpServer.create("localhost", this.port);
return httpServer.newHandler(adapter).block();
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.reactor.EnableRedisReactorSession;
@Import(EmbeddedRedisConfig.class)
// tag::class[]
@EnableRedisReactorSession
public class HelloWebfluxSessionConfig {
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
return new LettuceConnectionFactory();
}
}
// end::class[]

View File

@@ -1,91 +0,0 @@
/*
* =============================================================================
*
* Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
package sample;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SSEFluxWebConfig {
// TODO * Once there is a Spring Boot starter for thymeleaf-spring5, there would be no
// need to have
// TODO that @EnableConfigurationProperties annotation or use it for declaring the
// beans down in the
// TODO "thymeleaf" section below.
private ApplicationContext applicationContext;
public SSEFluxWebConfig(final ApplicationContext applicationContext) {
super();
this.applicationContext = applicationContext;
}
/*
* -------------------------------------- THYMELEAF CONFIGURATION
* --------------------------------------
*/
// TODO * If there was a Spring Boot starter for thymeleaf-spring5 most probably some
// or all of these
// TODO resolver and engine beans would not need to be specifically declared here.
@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.applicationContext);
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false);
resolver.setCheckExistence(true);
return resolver;
}
@Bean
public ISpringWebFluxTemplateEngine thymeleafTemplateEngine() {
// We override here the SpringTemplateEngine instance that would otherwise be
// instantiated by
// Spring Boot because we want to apply the SpringWebFlux-specific context
// factory, link builder...
final SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
@Bean
public ThymeleafReactiveViewResolver thymeleafChunkedAndDataDrivenViewResolver() {
final ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
viewResolver.setTemplateEngine(thymeleafTemplateEngine());
viewResolver.setOrder(1);
viewResolver.setResponseMaxChunkSizeBytes(8192); // OUTPUT BUFFER size limit
return viewResolver;
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
* @author Rob Winch
* @since 5.0
*/
@Configuration
public class ThymeleafWebfluxConfig implements WebFluxConfigurer {
@Autowired(required = false)
List<ViewResolver> views = new ArrayList<>();
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
for (ViewResolver view : this.views) {
registry.viewResolver(view);
}
}
}

View File

@@ -1,14 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.security" level="DEBUG"/> -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -43,6 +43,7 @@ public class Initializer implements ServletContextListener {
private HazelcastInstance instance;
@Override
public void contextInitialized(ServletContextEvent sce) {
this.instance = createHazelcastInstance();
Map<String, Session> sessions = this.instance.getMap(SESSION_MAP_NAME);
@@ -55,6 +56,7 @@ public class Initializer implements ServletContextListener {
fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
this.instance.shutdown();
}

View File

@@ -1,6 +1,5 @@
rootProject.name = 'spring-session-build'
FileTree buildFiles = fileTree(rootDir) {
include '**/*.gradle'
exclude '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*'
@@ -9,15 +8,14 @@ FileTree buildFiles = fileTree(rootDir) {
String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { File buildFile ->
boolean isDefaultName = 'build.gradle'.equals(buildFile.name)
if(isDefaultName) {
if (buildFile.name == 'build.gradle') {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replaceAll(File.separator, ':')
include projectPath
} else {
String projectName = buildFile.name.replace('.gradle', '');
String projectPath = ':' + projectName;
}
else {
String projectName = buildFile.name.replace('.gradle', '')
String projectPath = ':' + projectName
include projectPath
def project = findProject("${projectPath}")
project.name = projectName

View File

@@ -107,14 +107,17 @@ public final class MapSession implements Session, Serializable {
this.maxInactiveInterval = session.getMaxInactiveInterval();
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
this.lastAccessedTime = lastAccessedTime;
}
@Override
public Instant getCreationTime() {
return this.creationTime;
}
@Override
public String getId() {
return this.id;
}
@@ -127,24 +130,29 @@ public final class MapSession implements Session, Serializable {
this.originalId = originalId;
}
@Override
public String changeSessionId() {
String changedId = generateId();
setId(changedId);
return changedId;
}
@Override
public Instant getLastAccessedTime() {
return this.lastAccessedTime;
}
@Override
public void setMaxInactiveInterval(Duration interval) {
this.maxInactiveInterval = interval;
}
@Override
public Duration getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
@Override
public boolean isExpired() {
return isExpired(Instant.now());
}
@@ -156,15 +164,18 @@ public final class MapSession implements Session, Serializable {
return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAttribute(String attributeName) {
return (T) this.sessionAttrs.get(attributeName);
}
@Override
public Set<String> getAttributeNames() {
return this.sessionAttrs.keySet();
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
if (attributeValue == null) {
removeAttribute(attributeName);
@@ -174,6 +185,7 @@ public final class MapSession implements Session, Serializable {
}
}
@Override
public void removeAttribute(String attributeName) {
this.sessionAttrs.remove(attributeName);
}
@@ -198,10 +210,12 @@ public final class MapSession implements Session, Serializable {
this.id = id;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Session && this.id.equals(((Session) obj).getId());
}
@Override
public int hashCode() {
return this.id.hashCode();
}

View File

@@ -66,9 +66,10 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval);
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public void save(MapSession session) {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
@@ -77,6 +78,7 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
this.sessions.put(session.getId(), new MapSession(session));
}
@Override
public MapSession findById(String id) {
Session saved = this.sessions.get(id);
if (saved == null) {
@@ -89,10 +91,12 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
return new MapSession(saved);
}
@Override
public void deleteById(String id) {
this.sessions.remove(id);
}
@Override
public MapSession createSession() {
MapSession result = new MapSession();
if (this.defaultMaxInactiveInterval != null) {

View File

@@ -25,7 +25,7 @@ import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
/**
* A {@link ReactorSessionRepository} backed by a {@link Map} and that uses a
* A {@link ReactiveSessionRepository} backed by a {@link Map} and that uses a
* {@link MapSession}. The injected {@link java.util.Map} can be backed by a distributed
* NoSQL store like Hazelcast, for instance. Note that the supplied map itself is
* responsible for purging the expired sessions.
@@ -38,7 +38,7 @@ import org.springframework.session.events.SessionExpiredEvent;
* @author Rob Winch
* @since 2.0
*/
public class MapReactorSessionRepository implements ReactorSessionRepository<MapSession> {
public class ReactiveMapSessionRepository implements ReactiveSessionRepository<MapSession> {
/**
* If non-null, this value is used to override
@@ -54,7 +54,7 @@ public class MapReactorSessionRepository implements ReactorSessionRepository<Map
*
* @param sessions the {@link Map} to use. Cannot be null.
*/
public MapReactorSessionRepository(Map<String, Session> sessions) {
public ReactiveMapSessionRepository(Map<String, Session> sessions) {
if (sessions == null) {
throw new IllegalArgumentException("sessions cannot be null");
}
@@ -68,9 +68,10 @@ public class MapReactorSessionRepository implements ReactorSessionRepository<Map
* should be kept alive between client requests.
*/
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval);
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public Mono<Void> save(MapSession session) {
return Mono.fromRunnable(() -> {
if (!session.getId().equals(session.getOriginalId())) {
@@ -81,6 +82,7 @@ public class MapReactorSessionRepository implements ReactorSessionRepository<Map
});
}
@Override
public Mono<MapSession> findById(String id) {
// @formatter:off
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
@@ -90,10 +92,12 @@ public class MapReactorSessionRepository implements ReactorSessionRepository<Map
// @formatter:on
}
@Override
public Mono<Void> deleteById(String id) {
return Mono.fromRunnable(() -> this.sessions.remove(id));
}
@Override
public Mono<MapSession> createSession() {
return Mono.defer(() -> {
MapSession result = new MapSession();

View File

@@ -25,11 +25,11 @@ import reactor.core.publisher.Mono;
* @author Rob Winch
* @since 2.0
*/
public interface ReactorSessionRepository<S extends Session> {
public interface ReactiveSessionRepository<S extends Session> {
/**
* Creates a new {@link Session} that is capable of being persisted by this
* {@link ReactorSessionRepository}.
* {@link ReactiveSessionRepository}.
*
* <p>
* This allows optimizations and customizations in how the {@link Session} is
@@ -38,13 +38,13 @@ public interface ReactorSessionRepository<S extends Session> {
* </p>
*
* @return a new {@link Session} that is capable of being persisted by this
* {@link ReactorSessionRepository}
* {@link ReactiveSessionRepository}
*/
Mono<S> createSession();
/**
* Ensures the {@link Session} created by
* {@link ReactorSessionRepository#createSession()} is saved.
* {@link ReactiveSessionRepository#createSession()} is saved.
*
* <p>
* Some implementations may choose to save as the {@link Session} is updated by

View File

@@ -38,11 +38,10 @@ import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
@@ -97,7 +96,7 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();
private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();
private boolean usesSpringSessionRememberMeServices;
@@ -105,7 +104,7 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
private CookieSerializer cookieSerializer;
private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;
private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;
private List<HttpSessionListener> httpSessionListeners = new ArrayList<>();
@@ -113,7 +112,7 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
public void init() {
CookieSerializer cookieSerializer = this.cookieSerializer != null
? this.cookieSerializer : createDefaultCookieSerializer();
this.defaultHttpSessionStrategy.setCookieSerializer(cookieSerializer);
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
}
@Bean
@@ -127,16 +126,11 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
sessionRepositoryFilter.setHttpSessionStrategy(
(MultiHttpSessionStrategy) this.httpSessionStrategy);
}
else {
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
}
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (ClassUtils.isPresent(
@@ -159,8 +153,8 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
}
@Autowired(required = false)
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
this.httpSessionStrategy = httpSessionStrategy;
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
this.httpSessionIdResolver = httpSessionIdResolver;
}
@Autowired(required = false)

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session;
package org.springframework.session.config.annotation.web.server;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -24,7 +24,7 @@ import org.springframework.context.annotation.Import;
/**
* Add this annotation to a {@code @Configuration} class to configure a {@code WebSessionManager} for a WebFlux
* application. This annotation assumes a {@code ReactorSessionRepository} is defined somewhere in the application
* application. This annotation assumes a {@code ReactiveSessionRepository} is defined somewhere in the application
* context. If not, it will fail with a clear error messages. For example:
*
* <pre>
@@ -34,8 +34,8 @@ import org.springframework.context.annotation.Import;
* public class SpringWebFluxConfig {
*
* {@literal @Bean}
* public ReactorSessionRepository sessionRepository() {
* return new MapReactorSessionRepository();
* public ReactiveSessionRepository sessionRepository() {
* return new ReactiveMapSessionRepository();
* }
*
* }

View File

@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session;
package org.springframework.session.config.annotation.web.server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.server.session.SpringSessionWebSessionStore;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.DefaultWebSessionManager;
@@ -25,7 +27,7 @@ import org.springframework.web.server.session.WebSessionIdResolver;
import org.springframework.web.server.session.WebSessionManager;
/**
* Wire up a {@link WebSessionManager} using a Reactive {@link ReactorSessionRepository} from the application context.
* Wire up a {@link WebSessionManager} using a Reactive {@link ReactiveSessionRepository} from the application context.
*
* @author Greg Turnquist
* @author Rob Winch
@@ -43,13 +45,13 @@ public class SpringWebSessionConfiguration {
private WebSessionIdResolver webSessionIdResolver;
/**
* Configure a {@link WebSessionManager} using a provided {@link ReactorSessionRepository}.
* Configure a {@link WebSessionManager} using a provided {@link ReactiveSessionRepository}.
*
* @param repository - a bean that implements {@link ReactorSessionRepository}.
* @param repository - a bean that implements {@link ReactiveSessionRepository}.
* @return a configured {@link WebSessionManager} registered with a preconfigured name.
*/
@Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
public WebSessionManager webSessionManager(ReactorSessionRepository<? extends Session> repository) {
public WebSessionManager webSessionManager(ReactiveSessionRepository<? extends Session> repository) {
SpringSessionWebSessionStore<? extends Session> sessionStore = new SpringSessionWebSessionStore<>(repository);
DefaultWebSessionManager manager = new DefaultWebSessionManager();
manager.setSessionStore(sessionStore);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,16 +29,11 @@ import org.springframework.session.SessionRepository;
*/
@SuppressWarnings("serial")
public abstract class AbstractSessionEvent extends ApplicationEvent {
private final String sessionId;
private final Session session;
protected AbstractSessionEvent(Object source, String sessionId) {
super(source);
this.sessionId = sessionId;
this.session = null;
}
AbstractSessionEvent(Object source, Session session) {
super(source);
this.session = session;
@@ -62,4 +57,5 @@ public abstract class AbstractSessionEvent extends ApplicationEvent {
public String getSessionId() {
return this.sessionId;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,19 +25,14 @@ import org.springframework.session.SessionRepository;
*
* @author Rob Winch
* @since 1.0
*
*/
@SuppressWarnings("serial")
public class SessionCreatedEvent extends AbstractSessionEvent {
public SessionCreatedEvent(Object source, String sessionId) {
super(source, sessionId);
}
/**
* Create a new {@link SessionCreatedEvent}.
* @param source The Source of the SessionCreatedEvent
* @param session the Session that was created
* @param source the source of the event
* @param session the session that was created
*/
public SessionCreatedEvent(Object source, Session session) {
super(source, session);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,16 +26,17 @@ import org.springframework.session.SessionRepository;
* @author Mark Anderson
* @author Rob Winch
* @since 1.1
*
*/
@SuppressWarnings("serial")
public class SessionDeletedEvent extends SessionDestroyedEvent {
public SessionDeletedEvent(Object source, String sessionId) {
super(source, sessionId);
}
/**
* Create a new {@link SessionDeletedEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionDeletedEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,21 +23,17 @@ import org.springframework.session.Session;
*
* @author Rob Winch
* @since 1.0
*
*/
@SuppressWarnings("serial")
public class SessionDestroyedEvent extends AbstractSessionEvent {
public SessionDestroyedEvent(Object source, String sessionId) {
super(source, sessionId);
}
/**
* Create a new {@link SessionDestroyedEvent}.
* @param source The Source of the SessionDestoryedEvent
* @param session the Session that was created
* @param source the source of the event
* @param session the session that was created
*/
public SessionDestroyedEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,16 +26,17 @@ import org.springframework.session.SessionRepository;
* @author Mark Anderson
* @author Rob Winch
* @since 1.1
*
*/
@SuppressWarnings("serial")
public class SessionExpiredEvent extends SessionDestroyedEvent {
public SessionExpiredEvent(Object source, String sessionId) {
super(source, sessionId);
}
/**
* Create a new {@link SessionExpiredEvent}.
* @param source the source of the event
* @param session the session that was created
*/
public SessionExpiredEvent(Object source, Session session) {
super(source, session);
}
}

View File

@@ -55,12 +55,14 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
this.sessionRepository = sessionRepository;
}
@Override
public List<Object> getAllPrincipals() {
throw new UnsupportedOperationException("SpringSessionBackedSessionRegistry does "
+ "not support retrieving all principals, since Spring Session provides "
+ "no way to obtain that information");
}
@Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
@@ -77,6 +79,7 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
return infos;
}
@Override
public SessionInformation getSessionInformation(String sessionId) {
S session = this.sessionRepository.findById(sessionId);
if (session != null) {
@@ -89,18 +92,21 @@ public class SpringSessionBackedSessionRegistry<S extends Session>
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void refreshLastRequest(String sessionId) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void registerNewSession(String sessionId, Object principal) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
@Override
public void removeSessionInformation(String sessionId) {
}

View File

@@ -60,16 +60,19 @@ public class SpringSessionRememberMeServices
private String sessionAttrToDeleteOnLoginFail = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Override
public final Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
return null;
}
@Override
public final void loginFail(HttpServletRequest request,
HttpServletResponse response) {
logout(request);
}
@Override
public final void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
if (!this.alwaysRemember
@@ -126,6 +129,7 @@ public class SpringSessionRememberMeServices
this.validitySeconds = validitySeconds;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
logout(request);

View File

@@ -107,6 +107,7 @@ public abstract class AbstractHttpSessionApplicationInitializer
this.configurationClasses = configurationClasses;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
beforeSessionRepositoryFilter(servletContext);
if (this.configurationClasses != null) {

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
/**
* A {@link HttpSessionIdResolver} that uses a cookie to obtain the session from.
* Specifically, this implementation will allow specifying a cookie serialization strategy
* using {@link CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)}. The
* default is cookie name is "SESSION".
*
* When a session is created, the HTTP response will have a cookie with the specified
* cookie name and the value of the session id. The cookie will be marked as a session
* cookie, use the context path for the path of the cookie, marked as HTTPOnly, and if
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will
* be marked as secure. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
* </pre>
*
* The client should now include the session in each request by specifying the same cookie
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that expires the
* cookie. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public final class CookieHttpSessionIdResolver implements HttpSessionIdResolver {
private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
.getName().concat(".WRITTEN_SESSION_ID_ATTR");
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
return this.cookieSerializer.readCookieValues(request);
}
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
return;
}
request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, sessionId));
}
@Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
}
/**
* Sets the {@link CookieSerializer} to be used.
*
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
*/
public void setCookieSerializer(CookieSerializer cookieSerializer) {
if (cookieSerializer == null) {
throw new IllegalArgumentException("cookieSerializer cannot be null");
}
this.cookieSerializer = cookieSerializer;
}
}

View File

@@ -1,448 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.session.Session;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
/**
* A {@link HttpSessionStrategy} that uses a cookie to obtain the session from.
* Specifically, this implementation will allow specifying a cookie serialization strategy
* using {@link CookieHttpSessionStrategy#setCookieSerializer(CookieSerializer)}. The
* default is cookie name is "SESSION".
*
* When a session is created, the HTTP response will have a cookie with the specified
* cookie name and the value of the session id. The cookie will be marked as a session
* cookie, use the context path for the path of the cookie, marked as HTTPOnly, and if
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will
* be marked as secure. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
* </pre>
*
* The client should now include the session in each request by specifying the same cookie
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that expires the
* cookie. For example:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* <h2>Supporting Multiple Simultaneous Sessions</h2>
*
* <p>
* By default multiple sessions are also supported. Once a session is established with the
* browser, another session can be initiated by specifying a unique value for the
* {@link #setSessionAliasParamName(String)}. For example, a request to:
* </p>
*
* <pre>
* GET /messages/?_s=1416195761178 HTTP/1.1
* Host: example.com
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* Will result in the following response:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION="0 f81d4fae-7dec-11d0-a765-00a0c91e6bf6 1416195761178 8a929cde-2218-4557-8d4e-82a79a37876d"; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* <p>
* To use the original session a request without the HTTP parameter u can be made. To use
* the new session, a request with the HTTP parameter _s=1416195761178 can be used. By
* default URLs will be rewritten to include the currently selected session.
* </p>
*
* <h2>Selecting Sessions</h2>
*
* <p>
* Sessions can be managed by using the HttpSessionManager and SessionRepository. If you
* are not using Spring in the rest of your application you can obtain a reference from
* the HttpServletRequest attributes. An example is provided below:
* </p>
*
* <code>
* HttpSessionManager sessionManager =
* (HttpSessionManager) req.getAttribute(HttpSessionManager.class.getName());
* SessionRepository&lt;Session&gt; repo =
* (SessionRepository&lt;Session&gt;) req.getAttribute(SessionRepository.class.getName());
*
* String currentSessionAlias = sessionManager.getCurrentSessionAlias(req);
* Map&lt;String, String&gt; sessionIds = sessionManager.getSessionIds(req);
* String newSessionAlias = String.valueOf(System.currentTimeMillis());
*
* String contextPath = req.getContextPath();
* List&lt;Account&gt; accounts = new ArrayList&lt;&gt;();
* Account currentAccount = null; for(Map.Entry&lt;String, String&gt; entry :
* sessionIds.entrySet()) { String alias = entry.getKey(); String sessionId =
* entry.getValue();
* </code>
*
* Session session = repo.findById(sessionId); if(session == null) { continue; }
*
* String username = session.getAttribute("username"); if(username == null) {
* newSessionAlias = alias; continue; }
*
* String logoutUrl = sessionManager.encodeURL("./logout", alias); String switchAccountUrl
* = sessionManager.encodeURL("./", alias); Account account = new Account(username,
* logoutUrl, switchAccountUrl); if(currentSessionAlias.equals(alias)) { currentAccount =
* account; } else { accounts.add(account); } }
*
* req.setAttribute("currentAccount", currentAccount); req.setAttribute("addAccountUrl",
* sessionManager.encodeURL(contextPath, newSessionAlias)); req.setAttribute("accounts",
* accounts); }
*
*
* @author Rob Winch
* @since 1.0
*/
public final class CookieHttpSessionStrategy
implements MultiHttpSessionStrategy, HttpSessionManager {
/**
* The default delimiter for both serialization and deserialization.
*/
private static final String DEFAULT_DELIMITER = " ";
private static final String SESSION_IDS_WRITTEN_ATTR = CookieHttpSessionStrategy.class
.getName().concat(".SESSIONS_WRITTEN_ATTR");
static final String DEFAULT_ALIAS = "0";
static final String DEFAULT_SESSION_ALIAS_PARAM_NAME = "_s";
private static final Pattern ALIAS_PATTERN = Pattern.compile("^[\\w-]{1,50}$");
private String sessionParam = DEFAULT_SESSION_ALIAS_PARAM_NAME;
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
/**
* The delimiter between a session alias and a session id when reading a cookie value.
* The default value is " ".
*/
private String deserializationDelimiter = DEFAULT_DELIMITER;
/**
* The delimiter between a session alias and a session id when writing a cookie value.
* The default is " ".
*/
private String serializationDelimiter = DEFAULT_DELIMITER;
public String getRequestedSessionId(HttpServletRequest request) {
Map<String, String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
return sessionIds.get(sessionAlias);
}
public String getCurrentSessionAlias(HttpServletRequest request) {
if (this.sessionParam == null) {
return DEFAULT_ALIAS;
}
String u = request.getParameter(this.sessionParam);
if (u == null) {
return DEFAULT_ALIAS;
}
if (!ALIAS_PATTERN.matcher(u).matches()) {
return DEFAULT_ALIAS;
}
return u;
}
public String getNewSessionAlias(HttpServletRequest request) {
Set<String> sessionAliases = getSessionIds(request).keySet();
if (sessionAliases.isEmpty()) {
return DEFAULT_ALIAS;
}
long lastAlias = Long.decode(DEFAULT_ALIAS);
for (String alias : sessionAliases) {
long selectedAlias = safeParse(alias);
if (selectedAlias > lastAlias) {
lastAlias = selectedAlias;
}
}
return Long.toHexString(lastAlias + 1);
}
private long safeParse(String hex) {
try {
return Long.decode("0x" + hex);
}
catch (NumberFormatException notNumber) {
return 0;
}
}
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
Set<String> sessionIdsWritten = getSessionIdsWritten(request);
if (sessionIdsWritten.contains(session.getId())) {
return;
}
sessionIdsWritten.add(session.getId());
Map<String, String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
String cookieValue = createSessionCookieValue(sessionIds);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}
@SuppressWarnings("unchecked")
private Set<String> getSessionIdsWritten(HttpServletRequest request) {
Set<String> sessionsWritten = (Set<String>) request
.getAttribute(SESSION_IDS_WRITTEN_ATTR);
if (sessionsWritten == null) {
sessionsWritten = new HashSet<>();
request.setAttribute(SESSION_IDS_WRITTEN_ATTR, sessionsWritten);
}
return sessionsWritten;
}
private String createSessionCookieValue(Map<String, String> sessionIds) {
if (sessionIds.isEmpty()) {
return "";
}
if (sessionIds.size() == 1 && sessionIds.keySet().contains(DEFAULT_ALIAS)) {
return sessionIds.values().iterator().next();
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String id = entry.getValue();
sb.append(alias);
sb.append(this.serializationDelimiter);
sb.append(id);
sb.append(this.serializationDelimiter);
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
Map<String, String> sessionIds = getSessionIds(request);
String requestedAlias = getCurrentSessionAlias(request);
sessionIds.remove(requestedAlias);
String cookieValue = createSessionCookieValue(sessionIds);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}
/**
* Sets the name of the HTTP parameter that is used to specify the session alias. If
* the value is null, then only a single session is supported per browser.
*
* @param sessionAliasParamName the name of the HTTP parameter used to specify the
* session alias. If null, then ony a single session is supported per browser.
*/
public void setSessionAliasParamName(String sessionAliasParamName) {
this.sessionParam = sessionAliasParamName;
}
/**
* Sets the {@link CookieSerializer} to be used.
*
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
*/
public void setCookieSerializer(CookieSerializer cookieSerializer) {
if (cookieSerializer == null) {
throw new IllegalArgumentException("cookieSerializer cannot be null");
}
this.cookieSerializer = cookieSerializer;
}
/**
* Sets the delimiter between a session alias and a session id when deserializing a
* cookie. The default is " " This is useful when using
* <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> for writing the cookies
* which doesn't allow for spaces in the cookie values.
*
* @param delimiter the delimiter to set (i.e. "_ " will try a delimeter of either "_"
* or " ")
*/
public void setDeserializationDelimiter(String delimiter) {
this.deserializationDelimiter = delimiter;
}
/**
* Sets the delimiter between a session alias and a session id when deserializing a
* cookie. The default is " ". This is useful when using
* <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> for writing the cookies
* which doesn't allow for spaces in the cookie values.
*
* @param delimiter the delimiter to set (i.e. "_")
*/
public void setSerializationDelimiter(String delimiter) {
this.serializationDelimiter = delimiter;
}
public Map<String, String> getSessionIds(HttpServletRequest request) {
List<String> cookieValues = this.cookieSerializer.readCookieValues(request);
String sessionCookieValue = cookieValues.isEmpty() ? ""
: cookieValues.iterator().next();
Map<String, String> result = new LinkedHashMap<>();
StringTokenizer tokens = new StringTokenizer(sessionCookieValue,
this.deserializationDelimiter);
if (tokens.countTokens() == 1) {
result.put(DEFAULT_ALIAS, tokens.nextToken());
return result;
}
while (tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
if (!tokens.hasMoreTokens()) {
break;
}
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}
public HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response) {
request.setAttribute(HttpSessionManager.class.getName(), this);
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response) {
return new MultiSessionHttpServletResponse(response, request);
}
public String encodeURL(String url, String sessionAlias) {
String encodedSessionAlias = urlEncode(sessionAlias);
int queryStart = url.indexOf("?");
boolean isDefaultAlias = DEFAULT_ALIAS.equals(encodedSessionAlias);
if (queryStart < 0) {
return isDefaultAlias ? url
: url + "?" + this.sessionParam + "=" + encodedSessionAlias;
}
String path = url.substring(0, queryStart);
String query = url.substring(queryStart + 1, url.length());
String replacement = isDefaultAlias ? "" : "$1" + encodedSessionAlias;
query = query.replaceFirst("((^|&)" + this.sessionParam + "=)([^&]+)?",
replacement);
String sessionParamReplacement = String.format("%s=%s", this.sessionParam,
encodedSessionAlias);
if (!isDefaultAlias && !query.contains(sessionParamReplacement)
&& url.endsWith(query)) {
// no existing alias
if (!(query.endsWith("&") || query.length() == 0)) {
query += "&";
}
query += sessionParamReplacement;
}
return path + "?" + query;
}
private String urlEncode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* A {@link CookieHttpSessionStrategy} aware {@link HttpServletResponseWrapper}.
*/
class MultiSessionHttpServletResponse extends HttpServletResponseWrapper {
private final HttpServletRequest request;
MultiSessionHttpServletResponse(HttpServletResponse response,
HttpServletRequest request) {
super(response);
this.request = request;
}
private String getCurrentSessionAliasFromUrl(String url) {
String currentSessionAliasFromUrl = null;
int queryStart = url.indexOf("?");
if (queryStart >= 0) {
String query = url.substring(queryStart + 1);
Matcher matcher = Pattern
.compile(String.format("%s=([^&]+)",
CookieHttpSessionStrategy.this.sessionParam))
.matcher(query);
if (matcher.find()) {
currentSessionAliasFromUrl = matcher.group(1);
}
}
return currentSessionAliasFromUrl;
}
@Override
public String encodeRedirectURL(String url) {
String encodedUrl = super.encodeRedirectURL(url);
String currentSessionAliasFromUrl = getCurrentSessionAliasFromUrl(encodedUrl);
String alias = (currentSessionAliasFromUrl != null)
? currentSessionAliasFromUrl : getCurrentSessionAlias(this.request);
return CookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
@Override
public String encodeURL(String url) {
String encodedUrl = super.encodeURL(url);
String currentSessionAliasFromUrl = getCurrentSessionAliasFromUrl(encodedUrl);
String alias = (currentSessionAliasFromUrl != null)
? currentSessionAliasFromUrl : getCurrentSessionAlias(this.request);
return CookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
}
}

View File

@@ -63,6 +63,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
* @see org.springframework.session.web.http.CookieSerializer#readCookieValues(javax.
* servlet.http.HttpServletRequest)
*/
@Override
public List<String> readCookieValues(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
List<String> matchingCookieValues = new ArrayList<>();
@@ -91,6 +92,7 @@ public class DefaultCookieSerializer implements CookieSerializer {
* @see org.springframework.session.web.http.CookieWriter#writeCookieValue(org.
* springframework.session.web.http.CookieWriter.CookieValue)
*/
@Override
public void writeCookieValue(CookieValue cookieValue) {
HttpServletRequest request = cookieValue.getRequest();
HttpServletResponse response = cookieValue.getResponse();

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* A {@link HttpSessionIdResolver} that uses a header to resolve the session id.
* Specifically, this implementation will allow specifying a header name using
* {@link #HeaderHttpSessionIdResolver(String)}. Convenience factory methods for creating
* instances that use common header names, such as "X-Auth-Token" and
* "Authentication-Info", are available as well.
* <p>
* When a session is created, the HTTP response will have a response header of the
* specified name and the value of the session id. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* The client should now include the session in each request by specifying the same header
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that has the
* header name and a blank value. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token:
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
private static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info";
private final String headerName;
/**
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
* "X-Auth-Token" header.
* @return the instance configured to use "X-Auth-Token" header
*/
public static HeaderHttpSessionIdResolver xAuthToken() {
return new HeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);
}
/**
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
* "Authentication-Info" header.
* @return the instance configured to use "Authentication-Info" header
*/
public static HeaderHttpSessionIdResolver authenticationInfo() {
return new HeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);
}
/**
* The name of the header to obtain the session id from.
* @param headerName the name of the header to obtain the session id from.
*/
public HeaderHttpSessionIdResolver(String headerName) {
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
this.headerName = headerName;
}
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
return headerValue != null ? Collections.singletonList(headerValue)
: Collections.emptyList();
}
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
response.setHeader(this.headerName, sessionId);
}
@Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.session.Session;
/**
* A {@link HttpSessionStrategy} that uses a header to obtain the session from.
* Specifically, this implementation will allow specifying a header name using
* {@link HeaderHttpSessionStrategy#setHeaderName(String)}. The default is "X-Auth-Token".
*
* When a session is created, the HTTP response will have a response header of the
* specified name and the value of the session id. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* The client should now include the session in each request by specifying the same header
* in their request. For example:
*
* <pre>
* GET /messages/ HTTP/1.1
* Host: example.com
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* When the session is invalidated, the server will send an HTTP response that has the
* header name and a blank value. For example:
*
* <pre>
* HTTP/1.1 200 OK
* X-Auth-Token:
* </pre>
*
* @author Rob Winch
* @since 1.0
*/
public class HeaderHttpSessionStrategy implements HttpSessionStrategy {
private String headerName = "X-Auth-Token";
public String getRequestedSessionId(HttpServletRequest request) {
return request.getHeader(this.headerName);
}
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
response.setHeader(this.headerName, session.getId());
}
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
response.setHeader(this.headerName, "");
}
/**
* The name of the header to obtain the session id from. Default is "X-Auth-Token".
*
* @param headerName the name of the header to obtain the session id from.
*/
public void setHeaderName(String headerName) {
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
this.headerName = headerName;
}
}

View File

@@ -37,12 +37,22 @@ import org.springframework.session.Session;
*/
@SuppressWarnings("deprecation")
class HttpSessionAdapter<S extends Session> implements HttpSession {
private S session;
private final ServletContext servletContext;
private boolean invalidated;
private boolean old;
HttpSessionAdapter(S session, ServletContext servletContext) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
if (servletContext == null) {
throw new IllegalArgumentException("servletContext cannot be null");
}
this.session = session;
this.servletContext = servletContext;
}
@@ -55,74 +65,90 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
return this.session;
}
@Override
public long getCreationTime() {
checkState();
return this.session.getCreationTime().toEpochMilli();
}
@Override
public String getId() {
return this.session.getId();
}
@Override
public long getLastAccessedTime() {
checkState();
return this.session.getLastAccessedTime().toEpochMilli();
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public void setMaxInactiveInterval(int interval) {
this.session.setMaxInactiveInterval(Duration.ofSeconds(interval));
}
@Override
public int getMaxInactiveInterval() {
return (int) this.session.getMaxInactiveInterval().getSeconds();
}
@Override
public HttpSessionContext getSessionContext() {
return NOOP_SESSION_CONTEXT;
}
@Override
public Object getAttribute(String name) {
checkState();
return this.session.getAttribute(name);
}
@Override
public Object getValue(String name) {
return getAttribute(name);
}
@Override
public Enumeration<String> getAttributeNames() {
checkState();
return Collections.enumeration(this.session.getAttributeNames());
}
@Override
public String[] getValueNames() {
checkState();
Set<String> attrs = this.session.getAttributeNames();
return attrs.toArray(new String[0]);
}
@Override
public void setAttribute(String name, Object value) {
checkState();
this.session.setAttribute(name, value);
}
@Override
public void putValue(String name, Object value) {
setAttribute(name, value);
}
@Override
public void removeAttribute(String name) {
checkState();
this.session.removeAttribute(name);
}
@Override
public void removeValue(String name) {
removeAttribute(name);
}
@Override
public void invalidate() {
checkState();
this.invalidated = true;
@@ -132,6 +158,7 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
this.old = !isNew;
}
@Override
public boolean isNew() {
checkState();
return !this.old;
@@ -145,22 +172,31 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
}
private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() {
@Override
public HttpSession getSession(String sessionId) {
return null;
}
@Override
public Enumeration<String> getIds() {
return EMPTY_ENUMERATION;
}
};
private static final Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
throw new NoSuchElementException("a");
}
};
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Contract for session id resolution strategies. Allows for session id resolution through
* the request and for sending the session id or expiring the session through the
* response.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public interface HttpSessionIdResolver {
/**
* Resolve the session ids associated with the provided {@link HttpServletRequest}.
* For example, the session id might come from a cookie or a request header.
* @param request the current request
* @return the session ids
*/
List<String> resolveSessionIds(HttpServletRequest request);
/**
* Send the given session id to the client. This method is invoked when a new session
* is created and should inform a client what the new session id is. For example, it
* might create a new cookie with the session id in it or set an HTTP response header
* with the value of the new session id.
* @param request the current request
* @param response the current response
* @param sessionId the session id
*/
void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId);
/**
* Instruct the client to end the current session. This method is invoked when a
* session is invalidated and should inform a client that the session id is no longer
* valid. For example, it might remove a cookie with the session id in it or set an
* HTTP response header with an empty value indicating to the client to no longer
* submit that session id.
* @param request the current request
* @param response the current response
*/
void expireSession(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/**
* Allows managing a mapping of alias to the session id for having multiple active
* sessions at the same time.
*
* @author Rob Winch
* @since 1.0
*
*/
public interface HttpSessionManager {
/**
* Gets the current session's alias from the {@link HttpServletRequest}.
*
* @param request the {@link HttpServletRequest} to obtain the current session's alias
* from.
* @return the current sessions' alias. Cannot be null.
*/
String getCurrentSessionAlias(HttpServletRequest request);
/**
* Gets a mapping of the session alias to the session id from the
* {@link HttpServletRequest}.
*
* @param request the {@link HttpServletRequest} to obtain the mapping from. Cannot be
* null.
* @return a mapping of the session alias to the session id from the
* {@link HttpServletRequest}. Cannot be null.
*/
Map<String, String> getSessionIds(HttpServletRequest request);
/**
* Provides the ability to encode the URL for a given session alias.
*
* @param url the url to encode.
* @param sessionAlias the session alias to encode.
* @return the encoded URL
*/
String encodeURL(String url, String sessionAlias);
/**
* Gets a new and unique Session alias. Typically this will be called to pass into
* {@code HttpSessionManager#encodeURL(java.lang.String)}. For example:
*
* <code>
* String newAlias = httpSessionManager.getNewSessionAlias(request);
* String addAccountUrl = httpSessionManager.encodeURL("./", newAlias);
* </code>
*
* @param request the {@link HttpServletRequest} to get a new alias from
* @return Gets a new and unique Session alias.
*/
String getNewSessionAlias(HttpServletRequest request);
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.session.Session;
/**
* A strategy for mapping HTTP request and responses to a {@link Session}.
*
* @author Rob Winch
* @since 1.0
*/
public interface HttpSessionStrategy {
/**
* Obtains the requested session id from the provided
* {@link javax.servlet.http.HttpServletRequest}. For example, the session id might
* come from a cookie or a request header.
*
* @param request the {@link javax.servlet.http.HttpServletRequest} to obtain the
* session id from. Cannot be null.
* @return the {@link javax.servlet.http.HttpServletRequest} to obtain the session id
* from.
*/
String getRequestedSessionId(HttpServletRequest request);
/**
* This method is invoked when a new session is created and should inform a client
* what the new session id is. For example, it might create a new cookie with the
* session id in it or set an HTTP response header with the value of the new session
* id.
*
* Some implementations may wish to associate additional information to the
* {@link Session} at this time. For example, they may wish to add the IP Address,
* browser headers, the username, etc to the
* {@link org.springframework.session.Session}.
*
* @param session the {@link org.springframework.session.Session} that is being sent
* to the client. Cannot be null.
* @param request the {@link javax.servlet.http.HttpServletRequest} that create the
* new {@link org.springframework.session.Session} Cannot be null.
* @param response the {@link javax.servlet.http.HttpServletResponse} that is
* associated with the {@link javax.servlet.http.HttpServletRequest} that created the
* new {@link org.springframework.session.Session} Cannot be null.
*/
void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response);
/**
* This method is invoked when a session is invalidated and should inform a client
* that the session id is no longer valid. For example, it might remove a cookie with
* the session id in it or set an HTTP response header with an empty value indicating
* to the client to no longer submit that session id.
*
* @param request the {@link javax.servlet.http.HttpServletRequest} that invalidated
* the {@link org.springframework.session.Session} Cannot be null.
* @param response the {@link javax.servlet.http.HttpServletResponse} that is
* associated with the {@link javax.servlet.http.HttpServletRequest} that invalidated
* the {@link org.springframework.session.Session} Cannot be null.
*/
void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.web.http;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* Some {@link HttpSessionStrategy} may also want to further customize
* {@link HttpServletRequest} and {@link HttpServletResponse} objects. For example,
* {@link CookieHttpSessionStrategy} customizes how URL rewriting is done to select which
* session should be used in the event multiple sessions are active.
* </p>
*
* @author Rob Winch
* @since 1.0
* @see CookieHttpSessionStrategy
*/
public interface MultiHttpSessionStrategy
extends HttpSessionStrategy, RequestResponsePostProcessor {
}

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