Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676f0e474e | ||
|
|
e5ec612771 | ||
|
|
280d5c5a77 | ||
|
|
6a370b1ef8 | ||
|
|
41de1b087a | ||
|
|
6188fe68b7 | ||
|
|
ed328ff4b1 | ||
|
|
97ad0311e2 | ||
|
|
702bc37a99 | ||
|
|
17e56dda18 | ||
|
|
f5912da089 | ||
|
|
bff8ce3c03 | ||
|
|
a3803e9e1f | ||
|
|
3fcdc9ebce | ||
|
|
36d157a658 | ||
|
|
f28ab07b9a | ||
|
|
42a6001aae | ||
|
|
fc4d2238bc | ||
|
|
36d349f328 | ||
|
|
5f23a41674 | ||
|
|
4c9fbd5b6b | ||
|
|
f2ba773ec2 | ||
|
|
647dd7c7bb | ||
|
|
555223755d | ||
|
|
2e65d89ecc | ||
|
|
f3f18432ee | ||
|
|
03f6611e04 | ||
|
|
fff1d83097 | ||
|
|
91d4a5bfca | ||
|
|
34f29cf36c | ||
|
|
7e26897ec2 | ||
|
|
9ea1fb9af1 | ||
|
|
2c664d1d9e | ||
|
|
97698fd590 | ||
|
|
fe3f40c6f4 | ||
|
|
f8583bb02f | ||
|
|
5df555cd53 | ||
|
|
6f05c84aa7 | ||
|
|
cd394bbe10 | ||
|
|
2ecb2e60c0 | ||
|
|
d04a95ebfb | ||
|
|
858b52235e | ||
|
|
00ede81665 | ||
|
|
6cfa975b29 | ||
|
|
8b9d421ad6 | ||
|
|
df7ab9d99e | ||
|
|
7d61c5496a | ||
|
|
3492bc01d2 | ||
|
|
e08ac357dd | ||
|
|
1c29c7f14f |
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal 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.
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[]
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
springBootVersion=2.0.0.M4
|
||||
version=2.0.0.M5
|
||||
springBootVersion=2.0.0.M6
|
||||
version=2.0.0.RC2
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,3 @@ dependencies {
|
||||
integrationTestCompile seleniumDependencies
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
doFirst {
|
||||
systemProperties['spring.session.redis.namespace'] = project.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
BIN
samples/boot/webflux/src/main/resources/static/favicon.ico
Normal file
BIN
samples/boot/webflux/src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -36,6 +36,7 @@ public class WebSocketDisconnectHandler<S>
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SessionDisconnectEvent event) {
|
||||
String id = event.getSessionId();
|
||||
if (id == null) {
|
||||
|
||||
18
samples/gradle/dependency-management.gradle
Normal file
18
samples/gradle/dependency-management.gradle
Normal 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'
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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/*");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependency 'org.webjars:bootstrap:3.3.6'
|
||||
}
|
||||
}
|
||||
@@ -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']
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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[]
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
})();
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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[]
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Session> repo =
|
||||
* (SessionRepository<Session>) req.getAttribute(SessionRepository.class.getName());
|
||||
*
|
||||
* String currentSessionAlias = sessionManager.getCurrentSessionAlias(req);
|
||||
* Map<String, String> sessionIds = sessionManager.getSessionIds(req);
|
||||
* String newSessionAlias = String.valueOf(System.currentTimeMillis());
|
||||
*
|
||||
* String contextPath = req.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();
|
||||
* </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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user