Add CookieSerializer Strategy
This allows for custom seralization of the Cookie. Fixes gh-299
This commit is contained in:
101
docs/src/docs/asciidoc/guides/custom-cookie.adoc
Normal file
101
docs/src/docs/asciidoc/guides/custom-cookie.adoc
Normal file
@@ -0,0 +1,101 @@
|
||||
= Spring Session - Custom Cookie
|
||||
Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Spring Session to use custom cookies with Java Configuration.
|
||||
The guide assumes you have already link:./httpsession.html[setup Spring Session in your project].
|
||||
|
||||
NOTE: The completed guide can be found in the <<custom-cookie-sample, Custom Cookie sample application>>.
|
||||
|
||||
[[custom-cookie-spring-configuration]]
|
||||
== Spring Java Configuration
|
||||
|
||||
Once you have setup Spring Session you can easily customize how the session cookie is written by exposing a `CookieSerializer` as a Spring Bean.
|
||||
Out of the box, Spring Session comes with `DefaultCookieSerializer`.
|
||||
Simply exposing the `DefaultCookieSerializer` as a Spring Bean will augment the existing configuration when using configurations like `@EnableRedisHttpSession`.
|
||||
You can find an example of customizing Spring Session's cookie below:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}custom-cookie/src/main/java/sample/Config.java[tags=cookie-serializer]
|
||||
----
|
||||
|
||||
<1> We customize the name of the cookie to be JSESSIONID
|
||||
<2> We customize the path of the cookie to be "/" (rather than the default of the context root)
|
||||
<3> We customize the domain name pattern (a regular expression) to be `^.+?\\.(\\w+\\.[a-z]+)$`
|
||||
This allows sharing a session across domains and applications.
|
||||
If the regular expression does not match, no domain is set and the existing domain will be used.
|
||||
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] will be used as the domain.
|
||||
This means that a request to https://child.example.com will set the domain to example.com.
|
||||
However, a request to http://localhost:8080/ or http://192.168.1.100:8080/ will leave the cookie unset and thus still work in development without any changes necessary for production.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is important to note that users should only match on valid domain characters since the domain name is reflected in the response.
|
||||
This is prevent a malicious user from performing attacks like https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||
====
|
||||
|
||||
[[custom-cookie-options]]
|
||||
== Configuration Options
|
||||
|
||||
The configuration options available are:
|
||||
|
||||
* `cookieName` - the name of the cookie to use
|
||||
Default "SESSION"
|
||||
* `useSecureCookie` - specify if a secure cookie be used
|
||||
Default use value of `HttpServletRequest.isSecure()` at the time of creation.
|
||||
* `cookiePath` - the path of the cookie
|
||||
Default is context root
|
||||
* `cookieMaxAge` - specifies the max age of the cookie to be set at the time the session is created.
|
||||
Default is -1 which indicates the cookie will be removed when the browser is closed.
|
||||
* `jvmRoute` - specifies a suffix to be appended to the session id and included in the cookie.
|
||||
Used to identify which JVM to route to for session affinity.
|
||||
With some implementations (i.e. Redis) this provides no performance benefit.
|
||||
However, this can help with tracing logs of a particular user.
|
||||
* `domainName` - allows specifying a specific domain name to be used for the cookie.
|
||||
This option is simple to understand, but will likely require a different configuration between development and production environments.
|
||||
See domainNamePattern as an alternative.
|
||||
* `domainNamePattern` - a case insensitive pattern used to extract the domain name from the `HttpServletRequest#getServerName()`.
|
||||
The pattern should provide a single grouping used to extract the value of the cookie domain.
|
||||
If the regular expression does not match, no domain is set and the existing domain will be used.
|
||||
If the regular expression matches, the first https://docs.oracle.com/javase/tutorial/essential/regex/groups.html[grouping] will be used as the domain.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is important to note that users should only match on valid domain characters since the domain name is reflected in the response.
|
||||
This is prevent a malicious user from performing attacks like https://en.wikipedia.org/wiki/HTTP_response_splitting[HTTP Response Splitting].
|
||||
====
|
||||
|
||||
|
||||
[[custom-cookie-sample]]
|
||||
== custom-cookie Sample Application
|
||||
|
||||
|
||||
|
||||
=== Running the custom-cookie 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 `JedisConnectionFactory` to point to a Redis server.
|
||||
====
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:custom-cookie:tomcatRun
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the custom-cookie Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _rob_
|
||||
|
||||
Now click the **Set Attribute** button.
|
||||
You should now see the values displayed in the table.
|
||||
|
||||
If you look at the cookies for the application, you can see the cookie is saved to the custom name of JSESSIONID
|
||||
@@ -39,6 +39,10 @@ If you are looking to get started with Spring Session, the best place to start i
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store using XML based configuration.
|
||||
| link:guides/httpsession-xml.html[HttpSession XML Guide]
|
||||
|
||||
| {gh-samples-url}custom-cookie[Custom Cookie]
|
||||
| Demonstrates how to use Spring Session and customize the cookie.
|
||||
| link:guides/custom-cookie.html[Custom Cookie Guide]
|
||||
|
||||
| {gh-samples-url}boot[Spring Boot]
|
||||
| Demonstrates how to use Spring Session with Spring Boot.
|
||||
| link:guides/boot.html[Spring Boot Guide]
|
||||
@@ -345,7 +349,7 @@ This means if you require cleaning up expired sessions, you are responsible for
|
||||
[[api-enablehazelcasthttpsession]]
|
||||
=== EnableHazelcastHttpSession
|
||||
|
||||
If you wish to use http://hazelcast.org/[Hazelcast] as your backing source for the `SessionRepository`, then the `@EnableHazelcastHttpSession` annotation
|
||||
If you wish to use http://hazelcast.org/[Hazelcast] as your backing source for the `SessionRepository`, then the `@EnableHazelcastHttpSession` annotation
|
||||
can be added to an `@Configuration` class. This extends the functionality provided by the `@EnableSpringHttpSession` annotation but makes the `SessionRepository` for you in Hazelcast.
|
||||
You must provide a single `HazelcastInstance` bean for the configuration to work.
|
||||
For example:
|
||||
@@ -356,7 +360,7 @@ include::{docs-test-dir}docs/http/HazelcastHttpSessionConfig.java[tags=config]
|
||||
----
|
||||
|
||||
This will configure Hazelcast in embedded mode with default configuration.
|
||||
See the http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-configuration[Hazelcast documentation] for
|
||||
See the http://docs.hazelcast.org/docs/latest/manual/html-single/hazelcast-documentation.html#hazelcast-configuration[Hazelcast documentation] for
|
||||
detailed information on configuration options for Hazelcast.
|
||||
|
||||
[[api-enablehazelcasthttpsession-storage]]
|
||||
@@ -376,7 +380,7 @@ You can use the following attributes on `@EnableHazelcastHttpSession` to customi
|
||||
|
||||
[[api-enablehazelcasthttpsession-events]]
|
||||
==== Session Events
|
||||
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
|
||||
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
|
||||
publishing SessionCreatedEvent, SessionExpiredEvent, and SessionDeletedEvent events respectively using the `ApplicationEventPublisher`.
|
||||
|
||||
[[api-redisoperationssessionrepository]]
|
||||
@@ -626,7 +630,7 @@ The <<samples,Hazelcast Sample>> is a complete application demonstrating using S
|
||||
To run it use the following:
|
||||
|
||||
./gradlew :samples:hazelcast:tomcatRun
|
||||
|
||||
|
||||
The <<samples,Hazelcast Spring Sample>> is a complete application demonstrating using Spring Session with Hazelcast and Spring Security.
|
||||
|
||||
It includes example Hazelcast `MapListener` implementations that support firing `SessionCreatedEvent`, `SessionDeletedEvent` and `SessionExpiredEvent`.
|
||||
@@ -635,6 +639,9 @@ To run it use the following:
|
||||
|
||||
./gradlew :samples:hazelcast-spring:tomcatRun
|
||||
|
||||
[[api-mapsessionrepository-hazelcast]]
|
||||
==== Using Spring Session and Hazlecast
|
||||
|
||||
[[community]]
|
||||
== Spring Session Community
|
||||
|
||||
|
||||
19
samples/custom-cookie/build.gradle
Normal file
19
samples/custom-cookie/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_7_GRADLE
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile "junit:junit:$junitVersion"
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.spock.*
|
||||
import sample.pages.HomePage;
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
43
samples/custom-cookie/src/main/java/sample/Config.java
Normal file
43
samples/custom-cookie/src/main/java/sample/Config.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.session.web.http.CookieSerializer;
|
||||
import org.springframework.session.web.http.DefaultCookieSerializer;
|
||||
|
||||
|
||||
@EnableRedisHttpSession
|
||||
public class Config {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory connectionFactory() {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
|
||||
// tag::cookie-serializer[]
|
||||
@Bean
|
||||
public CookieSerializer cookieSerializer() {
|
||||
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
|
||||
serializer.setCookieName("JSESSIONID"); // <1>
|
||||
serializer.setCookiePath("/"); // <2>
|
||||
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); // <3>
|
||||
return serializer;
|
||||
}
|
||||
// end::cookie-serializer[]
|
||||
}
|
||||
28
samples/custom-cookie/src/main/java/sample/Initializer.java
Normal file
28
samples/custom-cookie/src/main/java/sample/Initializer.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.session.web.context.AbstractHttpSessionApplicationInitializer;
|
||||
|
||||
// tag::class[]
|
||||
public class Initializer
|
||||
extends AbstractHttpSessionApplicationInitializer { // <1>
|
||||
|
||||
public Initializer() {
|
||||
super(Config.class); // <2>
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.*;
|
||||
import javax.servlet.annotation.*;
|
||||
import javax.servlet.http.*;
|
||||
import java.io.IOException;
|
||||
|
||||
// tag::class[]
|
||||
@WebServlet("/session")
|
||||
public class SessionServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String attributeName = req.getParameter("attributeName");
|
||||
String attributeValue = req.getParameter("attributeValue");
|
||||
req.getSession().setAttribute(attributeName, attributeValue);
|
||||
resp.sendRedirect(req.getContextPath() + "/");
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 2878267318695777395L;
|
||||
}
|
||||
// tag::end[]
|
||||
5
samples/custom-cookie/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
5
samples/custom-cookie/src/main/webapp/assets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
48
samples/custom-cookie/src/main/webapp/index.jsp
Normal file
48
samples/custom-cookie/src/main/webapp/index.jsp
Normal file
@@ -0,0 +1,48 @@
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Session Attributes</title>
|
||||
<link rel="stylesheet" href="assets/bootstrap.min.css">
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Description</h1>
|
||||
<p>This application demonstrates how to use a Redis instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.</p>
|
||||
|
||||
<h1>Try it</h1>
|
||||
|
||||
<form class="form-inline" role="form" action="./session" method="post">
|
||||
<label for="attributeName">Attribute Name</label>
|
||||
<input id="attributeName" type="text" name="attributeName"/>
|
||||
<label for="attributeValue">Attribute Value</label>
|
||||
<input id="attributeValue" type="text" name="attributeValue"/>
|
||||
<input type="submit" value="Set Attribute"/>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute Name</th>
|
||||
<th>Attribute Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<c:forEach items="${sessionScope}" var="attr">
|
||||
<tr>
|
||||
<td><c:out value="${attr.key}"/></td>
|
||||
<td><c:out value="${attr.value}"/></td>
|
||||
</tr>
|
||||
</c:forEach>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@ rootProject.name = 'spring-session-build'
|
||||
include 'docs'
|
||||
|
||||
include 'samples:boot'
|
||||
include 'samples:custom-cookie'
|
||||
include 'samples:findbyusername'
|
||||
include 'samples:hazelcast'
|
||||
include 'samples:hazelcast-spring'
|
||||
|
||||
@@ -29,6 +29,8 @@ import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.session.web.http.CookieHttpSessionStrategy;
|
||||
import org.springframework.session.web.http.CookieSerializer;
|
||||
import org.springframework.session.web.http.HttpSessionStrategy;
|
||||
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@@ -82,10 +84,13 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@EnableScheduling
|
||||
public class SpringHttpSessionConfiguration {
|
||||
|
||||
private HttpSessionStrategy httpSessionStrategy;
|
||||
private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();
|
||||
|
||||
private HttpSessionStrategy httpSessionStrategy = defaultHttpSessionStrategy;
|
||||
|
||||
private List<HttpSessionListener> httpSessionListeners = new ArrayList<HttpSessionListener>();
|
||||
|
||||
|
||||
@Bean
|
||||
public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
|
||||
return new SessionEventHttpSessionListenerAdapter(httpSessionListeners);
|
||||
@@ -95,12 +100,15 @@ public class SpringHttpSessionConfiguration {
|
||||
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository, ServletContext servletContext) {
|
||||
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(sessionRepository);
|
||||
sessionRepositoryFilter.setServletContext(servletContext);
|
||||
if(httpSessionStrategy != null) {
|
||||
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
|
||||
}
|
||||
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
|
||||
return sessionRepositoryFilter;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setCookieSerializer(CookieSerializer cookieSerializer) {
|
||||
this.defaultHttpSessionStrategy.setCookieSerializer(cookieSerializer);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
|
||||
this.httpSessionStrategy = httpSessionStrategy;
|
||||
|
||||
@@ -19,18 +19,19 @@ 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.Pattern;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.Cookie;
|
||||
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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link HttpSessionStrategy} that uses a cookie to obtain the session from.
|
||||
@@ -161,11 +162,9 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
|
||||
private Pattern ALIAS_PATTERN = Pattern.compile("^[\\w-]{1,50}$");
|
||||
|
||||
private String cookieName = "SESSION";
|
||||
|
||||
private String sessionParam = DEFAULT_SESSION_ALIAS_PARAM_NAME;
|
||||
|
||||
private boolean isServlet3Plus = isServlet3();
|
||||
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
|
||||
|
||||
public String getRequestedSessionId(HttpServletRequest request) {
|
||||
Map<String,String> sessionIds = getSessionIds(request);
|
||||
@@ -220,8 +219,9 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
Map<String,String> sessionIds = getSessionIds(request);
|
||||
String sessionAlias = getCurrentSessionAlias(request);
|
||||
sessionIds.put(sessionAlias, session.getId());
|
||||
Cookie sessionCookie = createSessionCookie(request, sessionIds);
|
||||
response.addCookie(sessionCookie);
|
||||
|
||||
String cookieValue = createSessionCookieValue(sessionIds);
|
||||
cookieSerializer.writeCookieValue(new CookieValue(request,response,cookieValue));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -234,26 +234,14 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
return sessionsWritten;
|
||||
}
|
||||
|
||||
private Cookie createSessionCookie(HttpServletRequest request,
|
||||
Map<String, String> sessionIds) {
|
||||
Cookie sessionCookie = new Cookie(cookieName,"");
|
||||
if(isServlet3Plus) {
|
||||
sessionCookie.setHttpOnly(true);
|
||||
}
|
||||
sessionCookie.setSecure(request.isSecure());
|
||||
sessionCookie.setPath(cookiePath(request));
|
||||
// TODO set domain?
|
||||
|
||||
private String createSessionCookieValue(Map<String, String> sessionIds) {
|
||||
if(sessionIds.isEmpty()) {
|
||||
sessionCookie.setMaxAge(0);
|
||||
return sessionCookie;
|
||||
return "";
|
||||
}
|
||||
if(sessionIds.size() == 1) {
|
||||
return sessionIds.values().iterator().next();
|
||||
}
|
||||
|
||||
if(sessionIds.size() == 1) {
|
||||
String cookieValue = sessionIds.values().iterator().next();
|
||||
sessionCookie.setValue(cookieValue);
|
||||
return sessionCookie;
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for(Map.Entry<String,String> entry : sessionIds.entrySet()) {
|
||||
String alias = entry.getKey();
|
||||
@@ -265,9 +253,7 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
buffer.append(" ");
|
||||
}
|
||||
buffer.deleteCharAt(buffer.length()-1);
|
||||
|
||||
sessionCookie.setValue(buffer.toString());
|
||||
return sessionCookie;
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
|
||||
@@ -275,8 +261,8 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
String requestedAlias = getCurrentSessionAlias(request);
|
||||
sessionIds.remove(requestedAlias);
|
||||
|
||||
Cookie sessionCookie = createSessionCookie(request, sessionIds);
|
||||
response.addCookie(sessionCookie);
|
||||
String cookieValue = createSessionCookieValue(sessionIds);
|
||||
cookieSerializer.writeCookieValue(new CookieValue(request,response,cookieValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,45 +280,30 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the cookie to be used
|
||||
* @param cookieName the name of the cookie to be used
|
||||
* Sets the {@link CookieSerializer} to be used.
|
||||
*
|
||||
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
|
||||
*/
|
||||
public void setCookieName(String cookieName) {
|
||||
if(cookieName == null) {
|
||||
throw new IllegalArgumentException("cookieName cannot be null");
|
||||
}
|
||||
this.cookieName = cookieName;
|
||||
public void setCookieSerializer(CookieSerializer cookieSerializer) {
|
||||
Assert.notNull(cookieSerializer, "cookieSerializer cannot be null");
|
||||
this.cookieSerializer = cookieSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first cookie with the given name. Note that multiple
|
||||
* cookies can have the same name but different paths or domains.
|
||||
* @param request current servlet request
|
||||
* @param name cookie name
|
||||
* @return the first cookie with the given name, or {@code null} if none is found
|
||||
* Sets the name of the cookie to be used
|
||||
* @param cookieName the name of the cookie to be used
|
||||
* @deprecated use {@link #setCookieSerializer(CookieSerializer)}
|
||||
*/
|
||||
private static Cookie getCookie(HttpServletRequest request, String name) {
|
||||
if(request == null) {
|
||||
throw new IllegalArgumentException("request cannot be null");
|
||||
}
|
||||
Cookie cookies[] = request.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (name.equals(cookie.getName())) {
|
||||
return cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String cookiePath(HttpServletRequest request) {
|
||||
return request.getContextPath() + "/";
|
||||
@Deprecated
|
||||
public void setCookieName(String cookieName) {
|
||||
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
|
||||
serializer.setCookieName(cookieName);
|
||||
this.cookieSerializer = serializer;
|
||||
}
|
||||
|
||||
public Map<String,String> getSessionIds(HttpServletRequest request) {
|
||||
Cookie session = getCookie(request, cookieName);
|
||||
String sessionCookieValue = session == null ? "" : session.getValue();
|
||||
List<String> cookieValues = cookieSerializer.readCookieValues(request);
|
||||
String sessionCookieValue = cookieValues.isEmpty() ? "" : cookieValues.iterator().next();
|
||||
Map<String,String> result = new LinkedHashMap<String,String>();
|
||||
StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");
|
||||
if(tokens.countTokens() == 1) {
|
||||
@@ -411,16 +382,4 @@ public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Servlet 3 APIs are detected.
|
||||
* @return
|
||||
*/
|
||||
private boolean isServlet3() {
|
||||
try {
|
||||
ServletRequest.class.getMethod("startAsync");
|
||||
return true;
|
||||
} catch(NoSuchMethodException e) {}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Strategy for reading and writing a cookie value to the
|
||||
* {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface CookieSerializer {
|
||||
|
||||
/**
|
||||
* Writes a given {@link CookieValue} to the provided
|
||||
* {@link HttpServletResponse}
|
||||
*
|
||||
* @param cookieValue
|
||||
* the {@link CookieValue} to write to
|
||||
* {@link CookieValue#getResponse()}. Cannot be null.
|
||||
*/
|
||||
void writeCookieValue(CookieValue cookieValue);
|
||||
|
||||
/**
|
||||
* Reads all the matching cookies from the {@link HttpServletRequest}. The
|
||||
* result is a List since there can be multiple {@link Cookie} in a single
|
||||
* request with a matching name. For example, one Cookie may have a path of
|
||||
* / and another of /context, but the path is not transmitted in the
|
||||
* request.
|
||||
*
|
||||
* @param request
|
||||
* the {@link HttpServletRequest} to read the cookie from. Cannot
|
||||
* be null.
|
||||
* @return the values of all the matching cookies
|
||||
*/
|
||||
List<String> readCookieValues(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Contains the information necessary to write a value to the
|
||||
* {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class CookieValue {
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
private final String cookieValue;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param request
|
||||
* the {@link HttpServletRequest} to use. Useful for
|
||||
* determining the context in which the cookie is set. Cannot
|
||||
* be null.
|
||||
* @param response
|
||||
* the {@link HttpServletResponse} to use.
|
||||
* @param cookieValue
|
||||
* the value of the cookie to be written. This value may be
|
||||
* modified by the {@link CookieSerializer} when writing to
|
||||
* the actual cookie so long as the original value is
|
||||
* returned when the cookie is read.
|
||||
*/
|
||||
public CookieValue(HttpServletRequest request, HttpServletResponse response, String cookieValue) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.cookieValue = cookieValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request to use.
|
||||
* @return the request to use. Cannot be null.
|
||||
*/
|
||||
public HttpServletRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response to write to.
|
||||
* @return the response to write to. Cannot be null.
|
||||
*/
|
||||
public HttpServletResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to be written. This value may be modified by the {@link CookieSerializer} before written to the cookie. However, the value must be the same as the original when it is read back in.
|
||||
*
|
||||
* @return the value to be written
|
||||
*/
|
||||
public String getCookieValue() {
|
||||
return cookieValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* The default implementation of {@link CookieSerializer}
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class DefaultCookieSerializer implements CookieSerializer {
|
||||
private String cookieName = "SESSION";
|
||||
|
||||
private Boolean useSecureCookie;
|
||||
|
||||
private boolean useHttpOnlyCookie = isServlet3();
|
||||
|
||||
private String cookiePath;
|
||||
|
||||
private int cookieMaxAge = -1;
|
||||
|
||||
private String domainName;
|
||||
|
||||
private Pattern domainNamePattern;
|
||||
|
||||
private String jvmRoute;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.session.web.http.CookieSerializer#readCookieValues(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
public List<String> readCookieValues(HttpServletRequest request) {
|
||||
Cookie cookies[] = request.getCookies();
|
||||
List<String> matchingCookieValues = new ArrayList<String>();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookieName.equals(cookie.getName())) {
|
||||
String sessionId = cookie.getValue();
|
||||
if(jvmRoute != null && sessionId.endsWith(jvmRoute)) {
|
||||
sessionId = sessionId.substring(0, sessionId.length() - jvmRoute.length());
|
||||
}
|
||||
matchingCookieValues.add(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingCookieValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see
|
||||
* org.springframework.session.web.http.CookieWriter#writeCookieValue(org.
|
||||
* springframework.session.web.http.CookieWriter.CookieValue)
|
||||
*/
|
||||
public void writeCookieValue(CookieValue cookieValue) {
|
||||
HttpServletRequest request = cookieValue.getRequest();
|
||||
HttpServletResponse response = cookieValue.getResponse();
|
||||
|
||||
String requestedCookieValue = cookieValue.getCookieValue();
|
||||
String actualCookieValue = jvmRoute == null ? requestedCookieValue : requestedCookieValue + jvmRoute;
|
||||
|
||||
Cookie sessionCookie = new Cookie(cookieName, actualCookieValue);
|
||||
sessionCookie.setSecure(isSecureCookie(request));
|
||||
sessionCookie.setPath(getCookiePath(request));
|
||||
String domainName = getDomainName(request);
|
||||
if (domainName != null) {
|
||||
sessionCookie.setDomain(domainName);
|
||||
}
|
||||
|
||||
if (useHttpOnlyCookie) {
|
||||
sessionCookie.setHttpOnly(true);
|
||||
}
|
||||
|
||||
if ("".equals(requestedCookieValue)) {
|
||||
sessionCookie.setMaxAge(0);
|
||||
} else {
|
||||
sessionCookie.setMaxAge(cookieMaxAge);
|
||||
}
|
||||
|
||||
response.addCookie(sessionCookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a Cookie marked as secure should be used. The default is to use
|
||||
* the value of {@link HttpServletRequest#isSecure()}.
|
||||
*
|
||||
* @param useSecureCookie
|
||||
* determines if the cookie should be marked as secure.
|
||||
*/
|
||||
public void setUseSecureCookie(boolean useSecureCookie) {
|
||||
this.useSecureCookie = useSecureCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a Cookie marked as HTTP Only should be used. The default is true
|
||||
* in Servlet 3+ environments, else false.
|
||||
*
|
||||
* @param useHttpOnlyCookie
|
||||
* determines if the cookie should be marked as HTTP Only.
|
||||
*/
|
||||
public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) {
|
||||
if(useHttpOnlyCookie && !isServlet3()) {
|
||||
throw new IllegalArgumentException("You cannot set useHttpOnlyCookie to true in pre Servlet 3 environment");
|
||||
}
|
||||
this.useHttpOnlyCookie = useHttpOnlyCookie;
|
||||
}
|
||||
|
||||
private boolean isSecureCookie(HttpServletRequest request) {
|
||||
if (useSecureCookie == null) {
|
||||
return request.isSecure();
|
||||
}
|
||||
return useSecureCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path of the Cookie. The default is to use the context path from
|
||||
* the {@link HttpServletRequest}.
|
||||
*
|
||||
* @param cookiePath
|
||||
* the path of the Cookie. If null, the default of the context
|
||||
* path will be used.
|
||||
*/
|
||||
public void setCookiePath(String cookiePath) {
|
||||
this.cookiePath = cookiePath;
|
||||
}
|
||||
|
||||
public void setCookieName(String cookieName) {
|
||||
if (cookieName == null) {
|
||||
throw new IllegalArgumentException("cookieName cannot be null");
|
||||
}
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maxAge property of the Cookie. The default is -1 which signals
|
||||
* to delete the cookie when the browser is closed.
|
||||
*
|
||||
* @param cookieMaxAge
|
||||
* the maxAge property of the Cookie
|
||||
*/
|
||||
public void setCookieMaxAge(int cookieMaxAge) {
|
||||
this.cookieMaxAge = cookieMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an explicit Domain Name. This allow the domain of "example.com" to
|
||||
* be used when the request comes from www.example.com. This allows for
|
||||
* sharing the cookie across subdomains. The default is to use the current
|
||||
* domain.
|
||||
*
|
||||
* @param domainName
|
||||
* the name of the domain to use. (i.e. "example.com")
|
||||
* @throws IllegalStateException if the domainNamePattern is also set
|
||||
*/
|
||||
public void setDomainName(String domainName) {
|
||||
if (this.domainNamePattern != null) {
|
||||
throw new IllegalStateException("Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets a case insensitive pattern used to extract the domain name from the
|
||||
* {@link HttpServletRequest#getServerName()}. The pattern should provide a
|
||||
* single grouping that defines what the value is that should be matched.
|
||||
* User's should be careful not to output malicious characters like new
|
||||
* lines to prevent from things like
|
||||
* <a href= "https://www.owasp.org/index.php/HTTP_Response_Splitting">HTTP
|
||||
* Response Splitting</a>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the pattern does not match, then no domain will be set. This is useful
|
||||
* to ensure the domain is not set during development when localhost might
|
||||
* be used.
|
||||
* </p>
|
||||
* <p>
|
||||
* An example value might be "^.+?\\.(\\w+\\.[a-z]+)$". For the given input,
|
||||
* it would provide the following explicit domain (null means no domain name
|
||||
* is set):
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>example.com - null</li>
|
||||
* <li>child.sub.example.com - example.com</li>
|
||||
* <li>localhost - null</li>
|
||||
* <li>127.0.1.1 - null</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param domainNamePattern
|
||||
* the case insensitive pattern to extract the domain name with
|
||||
* @throws IllegalStateException if the domainName is also set
|
||||
*/
|
||||
public void setDomainNamePattern(String domainNamePattern) {
|
||||
if (this.domainName != null) {
|
||||
throw new IllegalStateException("Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
this.domainNamePattern = Pattern.compile(domainNamePattern, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Used to identify which JVM to route to for session affinity. With some
|
||||
* implementations (i.e. Redis) this provides no performance benefit.
|
||||
* However, this can help with tracing logs of a particular user.
|
||||
* </p>
|
||||
* <p>
|
||||
* To use set a custom route on each JVM instance and setup a frontend proxy
|
||||
* to forward all requests to the JVM based on the route.
|
||||
* </p>
|
||||
*
|
||||
* @param jvmRoute
|
||||
* the JVM Route to use (i.e. "node01jvmA", "n01ja", etc)
|
||||
*/
|
||||
public void setJvmRoute(String jvmRoute) {
|
||||
this.jvmRoute = jvmRoute;
|
||||
}
|
||||
|
||||
private String getDomainName(HttpServletRequest request) {
|
||||
if (domainName != null) {
|
||||
return domainName;
|
||||
}
|
||||
if (domainNamePattern != null) {
|
||||
Matcher matcher = domainNamePattern.matcher(request.getServerName());
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCookiePath(HttpServletRequest request) {
|
||||
if (cookiePath == null) {
|
||||
return request.getContextPath() + "/";
|
||||
}
|
||||
return cookiePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Servlet 3 APIs are detected.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isServlet3() {
|
||||
try {
|
||||
ServletRequest.class.getMethod("startAsync");
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.config.annotation.web.http;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.web.http.CookieSerializer;
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
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.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class EnableSpringHttpSessionCustomCookieSerializerTests {
|
||||
@Autowired
|
||||
MockHttpServletRequest request;
|
||||
@Autowired
|
||||
MockHttpServletResponse response;
|
||||
|
||||
MockFilterChain chain;
|
||||
|
||||
@Autowired
|
||||
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
|
||||
|
||||
@Autowired
|
||||
CookieSerializer cookieSerializer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
chain = new MockFilterChain();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesReadSessionIds() throws Exception {
|
||||
String sessionId = "sessionId";
|
||||
when(cookieSerializer.readCookieValues(any(HttpServletRequest.class))).thenReturn(Arrays.asList(sessionId));
|
||||
|
||||
sessionRepositoryFilter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(getRequest().getRequestedSessionId()).isEqualTo(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesWrite() throws Exception {
|
||||
sessionRepositoryFilter.doFilter(request, response, new MockFilterChain() {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
((HttpServletRequest) request).getSession();
|
||||
super.doFilter(request, response);
|
||||
}
|
||||
});
|
||||
|
||||
verify(cookieSerializer).writeCookieValue(any(CookieValue.class));
|
||||
}
|
||||
|
||||
private HttpServletRequest getRequest() {
|
||||
return (HttpServletRequest) chain.getRequest();
|
||||
}
|
||||
|
||||
@EnableSpringHttpSession
|
||||
@Configuration
|
||||
static class Config {
|
||||
@Bean
|
||||
public MapSessionRepository mapSessionRepository() {
|
||||
return new MapSessionRepository();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CookieSerializer cookieSerializer() {
|
||||
return mock(CookieSerializer.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,7 @@ public class CookieHttpSessionStrategyTests {
|
||||
assertThat(getSessionId()).isEqualTo(existing.getId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setCookieNameNull() throws Exception {
|
||||
strategy.setCookieName(null);
|
||||
@@ -439,6 +440,7 @@ public class CookieHttpSessionStrategyTests {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setCookieName(String cookieName) {
|
||||
strategy.setCookieName(cookieName);
|
||||
this.cookieName = cookieName;
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class DefaultCookieSerializerTests {
|
||||
|
||||
String cookieName;
|
||||
|
||||
MockHttpServletRequest request;
|
||||
|
||||
MockHttpServletResponse response;
|
||||
|
||||
DefaultCookieSerializer serializer;
|
||||
|
||||
String sessionId;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
cookieName = "SESSION";
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
sessionId = "sessionId";
|
||||
serializer = new DefaultCookieSerializer();
|
||||
}
|
||||
|
||||
// --- readCookieValues ---
|
||||
|
||||
@Test
|
||||
public void readCookieValuesNull() {
|
||||
assertThat(serializer.readCookieValues(request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieValuesSingle() {
|
||||
request.setCookies(new Cookie(cookieName, sessionId));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsOnly(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieValuesSingleAndInvalidName() {
|
||||
request.setCookies(new Cookie(cookieName, sessionId), new Cookie(cookieName+"INVALID", sessionId + "INVALID"));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsOnly(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieValuesMulti() {
|
||||
String secondSession = "secondSessionId";
|
||||
request.setCookies(new Cookie(cookieName, sessionId), new Cookie(cookieName, secondSession));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsExactly(sessionId, secondSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieValuesMultiCustomSessionCookieName() {
|
||||
setCookieName("JSESSIONID");
|
||||
String secondSession = "secondSessionId";
|
||||
request.setCookies(new Cookie(cookieName, sessionId), new Cookie(cookieName, secondSession));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsExactly(sessionId, secondSession);
|
||||
}
|
||||
|
||||
// --- writeCookie ---
|
||||
|
||||
@Test
|
||||
public void writeCookie() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getValue()).isEqualTo(sessionId);
|
||||
}
|
||||
|
||||
// --- httpOnly ---
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlyDefault() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlySetTrue() {
|
||||
serializer.setUseHttpOnlyCookie(true);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlySetFalse() {
|
||||
serializer.setUseHttpOnlyCookie(false);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isFalse();
|
||||
}
|
||||
|
||||
// --- domainName ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNameDefault() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNameCustom() {
|
||||
String domainName = "example.com";
|
||||
serializer.setDomainName(domainName);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isEqualTo(domainName);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void setDomainNameAndDomainNamePatternThrows() {
|
||||
serializer.setDomainName("example.com");
|
||||
serializer.setDomainNamePattern(".*");
|
||||
}
|
||||
|
||||
// --- domainNamePattern ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNamePattern() {
|
||||
String domainNamePattern = "^.+?\\.(\\w+\\.[a-z]+)$";
|
||||
serializer.setDomainNamePattern(domainNamePattern);
|
||||
|
||||
String[] matchingDomains = {"child.sub.example.com","www.example.com"};
|
||||
for(String domain : matchingDomains) {
|
||||
request.setServerName(domain);
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
assertThat(getCookie().getDomain()).isEqualTo("example.com");
|
||||
|
||||
response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
String[] notMatchingDomains = {"example.com", "localhost","127.0.0.1"};
|
||||
for(String domain : notMatchingDomains) {
|
||||
request.setServerName(domain);
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
|
||||
response = new MockHttpServletResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void setDomainNamePatternAndDomainNameThrows() {
|
||||
serializer.setDomainNamePattern(".*");
|
||||
serializer.setDomainName("example.com");
|
||||
}
|
||||
|
||||
// --- cookieName ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieNameDefault() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo("SESSION");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieNameCustom() {
|
||||
String cookieName = "JSESSIONID";
|
||||
setCookieName(cookieName);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo(cookieName);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setCookieNameNullThrows() {
|
||||
serializer.setCookieName(null);
|
||||
}
|
||||
|
||||
// --- cookiePath ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathDefaultEmptyContextPathUsed() {
|
||||
request.setContextPath("");
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathDefaultContextPathUsed() {
|
||||
request.setContextPath("/context");
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathExplicitNullCookiePathContextPathUsed() {
|
||||
request.setContextPath("/context");
|
||||
serializer.setCookiePath(null);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathExplicitCookiePath() {
|
||||
request.setContextPath("/context");
|
||||
serializer.setCookiePath("/");
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
// --- cookieMaxAge ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeDefault() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeExplicit() {
|
||||
serializer.setCookieMaxAge(100);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeExplicitEmptyCookie() {
|
||||
serializer.setCookieMaxAge(100);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(""));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(0);
|
||||
}
|
||||
|
||||
// --- secure ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDefaultInsecureRequest() {
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSecureSecureRequest() {
|
||||
request.setSecure(true);
|
||||
serializer.setUseSecureCookie(true);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSecureInsecureRequest() {
|
||||
serializer.setUseSecureCookie(true);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieInsecureSecureRequest() {
|
||||
request.setSecure(true);
|
||||
serializer.setUseSecureCookie(false);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieInecureInsecureRequest() {
|
||||
serializer.setUseSecureCookie(false);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
// --- jvmRoute ---
|
||||
|
||||
@Test
|
||||
public void writeCookieJvmRoute() {
|
||||
String jvmRoute = "route";
|
||||
serializer.setJvmRoute(jvmRoute);
|
||||
|
||||
serializer.writeCookieValue(cookieValue(sessionId));
|
||||
|
||||
assertThat(getCookie().getValue()).isEqualTo(sessionId + jvmRoute);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieJvmRoute() {
|
||||
String jvmRoute = "route";
|
||||
serializer.setJvmRoute(jvmRoute);
|
||||
request.setCookies(new Cookie(cookieName, sessionId + jvmRoute));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsOnly(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieJvmRouteRouteMissing() {
|
||||
String jvmRoute = "route";
|
||||
serializer.setJvmRoute(jvmRoute);
|
||||
request.setCookies(new Cookie(cookieName, sessionId));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsOnly(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieJvmRouteOnlyRoute() {
|
||||
String jvmRoute = "route";
|
||||
serializer.setJvmRoute(jvmRoute);
|
||||
request.setCookies(new Cookie(cookieName, jvmRoute));
|
||||
|
||||
assertThat(serializer.readCookieValues(request)).containsOnly("");
|
||||
}
|
||||
|
||||
public void setCookieName(String cookieName) {
|
||||
this.cookieName = cookieName;
|
||||
this.serializer.setCookieName(cookieName);
|
||||
}
|
||||
|
||||
private Cookie getCookie() {
|
||||
return response.getCookie(cookieName);
|
||||
}
|
||||
|
||||
private CookieValue cookieValue(String cookieValue) {
|
||||
return new CookieValue(request, response, cookieValue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user