Add support for multiple sessions in a browser

Fixes gh-49
This commit is contained in:
Rob Winch
2014-11-16 22:13:27 -06:00
parent 9df0594573
commit a0805e8411
26 changed files with 1509 additions and 105 deletions

View File

@@ -15,28 +15,38 @@
*/
package org.springframework.session.web.http;
import org.springframework.session.Session;
import org.springframework.util.Assert;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
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.util.Assert;
/**
* A {@link HttpSessionStrategy} that uses a cookie to obtain the session from. Specifically, this implementation will
* allow specifying a cookie name using {@link CookieHttpSessionStrategy#setCookieName(String)}. The default is "SESSION".
* A {@link HttpSessionStrategy} that uses a cookie to obtain the session from.
* Specifically, this implementation will allow specifying a cookie name using
* {@link CookieHttpSessionStrategy#setCookieName(String)}. The default 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:
* 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:
* 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
@@ -44,46 +54,194 @@ import javax.servlet.http.HttpServletResponse;
* 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:
* 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/?u=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
* u=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();
*
* Session session = repo.getSession(sessionId);
* if(session == null) {
* continue;
* }
*
* String username = (String) 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);
* </code>
*
*
* @since 1.0
* @author Rob Winch
*/
public final class CookieHttpSessionStrategy implements HttpSessionStrategy {
public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy, HttpSessionManager {
static final String DEFAULT_ALIAS = "0";
static final String DEFAULT_SESSION_ALIAS_PARAM_NAME = "u";
private String cookieName = "SESSION";
private String sessionParam = DEFAULT_SESSION_ALIAS_PARAM_NAME;
@Override
public String getRequestedSessionId(HttpServletRequest request) {
Cookie session = getCookie(request, cookieName);
return session == null ? null : session.getValue();
Map<String,String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
return sessionIds.get(sessionAlias);
}
@Override
public String getCurrentSessionAlias(HttpServletRequest request) {
if(sessionParam == null) {
return DEFAULT_ALIAS;
}
String u = request.getParameter(sessionParam);
if(u == null) {
return DEFAULT_ALIAS;
}
return u;
}
@Override
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
Cookie cookie = new Cookie(cookieName, session.getId());
cookie.setHttpOnly(true);
cookie.setSecure(request.isSecure());
cookie.setPath(cookiePath(request));
response.addCookie(cookie);
Map<String,String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
Cookie sessionCookie = createSessionCookie(request, sessionIds);
response.addCookie(sessionCookie);
}
// TODO set the domain?
private Cookie createSessionCookie(HttpServletRequest request,
Map<String, String> sessionIds) {
Cookie sessionCookie = new Cookie(cookieName,"");
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(request.isSecure());
sessionCookie.setPath(cookiePath(request));
// TODO set domain?
if(sessionIds.isEmpty()) {
sessionCookie.setMaxAge(0);
return sessionCookie;
}
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();
String id = entry.getValue();
buffer.append(alias);
buffer.append(" ");
buffer.append(id);
buffer.append(" ");
}
buffer.deleteCharAt(buffer.length()-1);
sessionCookie.setValue(buffer.toString());
return sessionCookie;
}
@Override
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
Cookie sessionCookie = new Cookie(cookieName,"");
sessionCookie.setMaxAge(0);
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(request.isSecure());
sessionCookie.setPath(cookiePath(request));
Map<String,String> sessionIds = getSessionIds(request);
String requestedAlias = getCurrentSessionAlias(request);
sessionIds.remove(requestedAlias);
Cookie sessionCookie = createSessionCookie(request, sessionIds);
response.addCookie(sessionCookie);
}
/**
* 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 name of the cookie to be used
* @param cookieName
@@ -116,4 +274,78 @@ public final class CookieHttpSessionStrategy implements HttpSessionStrategy {
private static String cookiePath(HttpServletRequest request) {
return request.getContextPath() + "/";
}
@Override
public Map<String,String> getSessionIds(HttpServletRequest request) {
Cookie session = getCookie(request, cookieName);
String sessionCookieValue = session == null ? "" : session.getValue();
Map<String,String> result = new LinkedHashMap<String,String>();
StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");
if(tokens.countTokens() == 1) {
result.put(DEFAULT_ALIAS, tokens.nextToken());
return result;
}
while(tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}
@Override
public HttpServletRequest wrapRequest(HttpServletRequest request, HttpServletResponse response) {
request.setAttribute(HttpSessionManager.class.getName(), this);
return request;
}
@Override
public HttpServletResponse wrapResponse(HttpServletRequest request, HttpServletResponse response) {
return new MultiSessionHttpServletResponse(response, request);
}
class MultiSessionHttpServletResponse extends HttpServletResponseWrapper {
private final HttpServletRequest request;
public MultiSessionHttpServletResponse(HttpServletResponse response, HttpServletRequest request) {
super(response);
this.request = request;
}
@Override
public String encodeRedirectURL(String url) {
url = super.encodeRedirectURL(url);
return CookieHttpSessionStrategy.this.encodeURL(url, getCurrentSessionAlias(request));
}
@Override
public String encodeURL(String url) {
url = super.encodeURL(url);
String alias = getCurrentSessionAlias(request);
return CookieHttpSessionStrategy.this.encodeURL(url, alias);
}
}
@Override
public String encodeURL(String url, String sessionAlias) {
int queryStart = url.indexOf("?");
boolean isDefaultAlias = DEFAULT_ALIAS.equals(sessionAlias);
if(queryStart < 0) {
return isDefaultAlias ? url : url + "?" + sessionParam + "=" + sessionAlias;
}
String path = url.substring(0, queryStart);
String query = url.substring(queryStart + 1, url.length());
String replacement = isDefaultAlias ? "" : "$1"+sessionAlias;
query = query.replaceFirst( "((^|&)" + sessionParam + "=)([^&]+)?", replacement);
if(!isDefaultAlias && url.endsWith(query)) {
// no existing alias
if(!(query.endsWith("&") || query.length() == 0)) {
query += "&";
}
query += sessionParam + "=" + sessionAlias;
}
return path + "?" + query;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2013 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);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2013 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>
*
* @see CookieHttpSessionStrategy
*
* @author Rob Winch
* @since 1.0
*/
public interface MultiHttpSessionStrategy extends HttpSessionStrategy, RequestResponsePostProcessor {
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2013 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;
/**
* Allows customizing the {@link HttpServletRequest} and/or the
* {@link HttpServletResponse}.
*
* @author Rob Winch
* @since 1.0
*/
public interface RequestResponsePostProcessor {
/**
* Allows customizing the {@link HttpServletRequest}.
*
* @param request
* the original {@link HttpServletRequest}. Cannot be null.
* @param response
* the original {@link HttpServletResponse}. This is NOT the
* result of
* {@link #wrapResponse(HttpServletRequest, HttpServletResponse)}
* Cannot be null. .
* @return a non-null {@link HttpServletRequest}
*/
HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response);
/**
* Allows customizing the {@link HttpServletResponse}.
*
* @param request
* the original {@link HttpServletRequest}. This is NOT the
* result of
* {@link #wrapRequest(HttpServletRequest, HttpServletResponse)}.
* Cannot be null.
* @param response
* the original {@link HttpServletResponse}. Cannot be null.
* @return a non-null {@link HttpServletResponse}
*/
HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response);
}

View File

@@ -17,6 +17,7 @@ package org.springframework.session.web.http;
import org.springframework.core.Ordered;
import org.springframework.session.ExpiringSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
@@ -53,13 +54,15 @@ import java.util.Set;
* @author Rob Winch
*/
public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter implements Ordered {
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class.getName();
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 50;
private int order = DEFAULT_ORDER;
private final SessionRepository<S> sessionRepository;
private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
/**
* Creates a new instance
@@ -77,15 +80,31 @@ public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerR
* @param httpSessionStrategy the {@link HttpSessionStrategy} to use. Cannot be null.
*/
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
Assert.notNull(httpSessionStrategy,"httpSessionIdStrategy cannot be null");
this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter(httpSessionStrategy);
}
/**
* Sets the {@link MultiHttpSessionStrategy} to be used. The default is a {@link CookieHttpSessionStrategy}.
*
* @param httpSessionStrategy the {@link MultiHttpSessionStrategy} to use. Cannot be null.
*/
public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) {
Assert.notNull(httpSessionStrategy,"httpSessionIdStrategy cannot be null");
this.httpSessionStrategy = httpSessionStrategy;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,response);
HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
filterChain.doFilter(strategyRequest, strategyResponse);
} finally {
wrappedRequest.commitSession();
}
@@ -145,7 +164,9 @@ public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerR
} else {
S session = wrappedSession.session;
sessionRepository.save(session);
httpSessionStrategy.onNewSession(session, this, response);
if(!requestedValidSession) {
httpSessionStrategy.onNewSession(session, this, response);
}
}
}
@@ -346,4 +367,38 @@ public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerR
public int getOrder() {
return order;
}
static class MultiHttpSessionStrategyAdapter implements MultiHttpSessionStrategy {
private HttpSessionStrategy delegate;
public MultiHttpSessionStrategyAdapter(HttpSessionStrategy delegate) {
this.delegate = delegate;
}
public String getRequestedSessionId(HttpServletRequest request) {
return delegate.getRequestedSessionId(request);
}
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
delegate.onNewSession(session, request, response);
}
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
delegate.onInvalidateSession(request, response);
}
@Override
public HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response) {
return request;
}
@Override
public HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response) {
return response;
}
}
}

View File

@@ -13,99 +13,238 @@ import org.springframework.session.web.http.CookieHttpSessionStrategy;
import javax.servlet.http.Cookie;
public class CookieHttpSessionStrategyTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private CookieHttpSessionStrategy strategy;
private String cookieName;
private Session session;
private CookieHttpSessionStrategy strategy;
private String cookieName;
private Session session;
@Before
public void setup() throws Exception {
cookieName = "SESSION";
session = new MapSession();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
strategy = new CookieHttpSessionStrategy();
}
@Before
public void setup() throws Exception {
cookieName = "SESSION";
session = new MapSession();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
strategy = new CookieHttpSessionStrategy();
}
@Test
public void getRequestedSessionIdNull() throws Exception {
assertThat(strategy.getRequestedSessionId(request)).isNull();
}
@Test
public void getRequestedSessionIdNull() throws Exception {
assertThat(strategy.getRequestedSessionId(request)).isNull();
}
@Test
public void getRequestedSessionIdNotNull() throws Exception {
setSessionId(session.getId());
assertThat(strategy.getRequestedSessionId(request)).isEqualTo(session.getId());
}
@Test
public void getRequestedSessionIdNotNull() throws Exception {
setSessionId(session.getId());
assertThat(strategy.getRequestedSessionId(request)).isEqualTo(session.getId());
}
@Test
public void getRequestedSessionIdNotNullCustomCookieName() throws Exception {
setCookieName("CUSTOM");
setSessionId(session.getId());
assertThat(strategy.getRequestedSessionId(request)).isEqualTo(session.getId());
}
@Test
public void getRequestedSessionIdNotNullCustomCookieName() throws Exception {
setCookieName("CUSTOM");
setSessionId(session.getId());
assertThat(strategy.getRequestedSessionId(request)).isEqualTo(session.getId());
}
@Test
public void onNewSession() throws Exception {
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
@Test
public void onNewSession() throws Exception {
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
@Test
public void onNewSessionCookiePath() throws Exception {
request.setContextPath("/somethingunique");
strategy.onNewSession(session, request, response);
@Test
public void onNewSessionExistingSessionSameAlias() throws Exception {
Session existing = new MapSession();
setSessionId(existing.getId());
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
Cookie sessionCookie = response.getCookie(cookieName);
assertThat(sessionCookie.getPath()).isEqualTo(request.getContextPath() + "/");
}
@Test
public void onNewSessionExistingSessionNewAlias() throws Exception {
Session existing = new MapSession();
setSessionId(existing.getId());
request.setParameter(CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo("0 " + existing.getId() + " new " + session.getId());
}
@Test
public void onNewSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
@Test
public void onNewSessionCookiePath() throws Exception {
request.setContextPath("/somethingunique");
strategy.onNewSession(session, request, response);
@Test
public void onDeleteSession() throws Exception {
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEmpty();
}
Cookie sessionCookie = response.getCookie(cookieName);
assertThat(sessionCookie.getPath()).isEqualTo(request.getContextPath() + "/");
}
@Test
public void onDeleteSessionCookiePath() throws Exception {
request.setContextPath("/somethingunique");
strategy.onInvalidateSession(request, response);
@Test
public void onNewSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
strategy.onNewSession(session, request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
Cookie sessionCookie = response.getCookie(cookieName);
assertThat(sessionCookie.getPath()).isEqualTo(request.getContextPath() + "/");
}
@Test
public void onDeleteSession() throws Exception {
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEmpty();
}
@Test
public void onDeleteSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEmpty();
}
@Test
public void onDeleteSessionCookiePath() throws Exception {
request.setContextPath("/somethingunique");
strategy.onInvalidateSession(request, response);
@Test(expected = IllegalArgumentException.class)
public void setCookieNameNull() throws Exception {
strategy.setCookieName(null);
}
Cookie sessionCookie = response.getCookie(cookieName);
assertThat(sessionCookie.getPath()).isEqualTo(request.getContextPath() + "/");
}
public void setCookieName(String cookieName) {
strategy.setCookieName(cookieName);
this.cookieName = cookieName;
}
@Test
public void onDeleteSessionCustomCookieName() throws Exception {
setCookieName("CUSTOM");
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEmpty();
}
public void setSessionId(String id) {
request.setCookies(new Cookie(cookieName, id));
}
@Test
public void onDeleteSessionExistingSessionSameAlias() throws Exception {
Session existing = new MapSession();
setSessionId("0 " + existing.getId() + " new " + session.getId());
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEqualTo(session.getId());
}
public String getSessionId() {
return response.getCookie(cookieName).getValue();
}
@Test
public void onDeleteSessionExistingSessionNewAlias() throws Exception {
Session existing = new MapSession();
setSessionId("0 " + existing.getId() + " new " + session.getId());
request.setParameter(CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
strategy.onInvalidateSession(request, response);
assertThat(getSessionId()).isEqualTo(existing.getId());
}
@Test(expected = IllegalArgumentException.class)
public void setCookieNameNull() throws Exception {
strategy.setCookieName(null);
}
@Test
public void encodeURLNoExistingQuery() {
assertThat(strategy.encodeURL("/url", "2")).isEqualTo("/url?u=2");
}
@Test
public void encodeURLNoExistingQueryEmpty() {
assertThat(strategy.encodeURL("/url?", "2")).isEqualTo("/url?u=2");
}
@Test
public void encodeURLExistingQueryNoAlias() {
assertThat(strategy.encodeURL("/url?a=b", "2")).isEqualTo("/url?a=b&u=2");
}
@Test
public void encodeURLExistingQueryExistingAliasStart() {
assertThat(strategy.encodeURL("/url?u=1&y=z", "2")).isEqualTo("/url?u=2&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasMiddle() {
assertThat(strategy.encodeURL("/url?a=b&u=1&y=z", "2")).isEqualTo("/url?a=b&u=2&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasEnd() {
assertThat(strategy.encodeURL("/url?a=b&u=1", "2")).isEqualTo("/url?a=b&u=2");
}
//
@Test
public void encodeURLExistingQueryParamEndsWithActualParamStart() {
assertThat(strategy.encodeURL("/url?xu=1&y=z", "2")).isEqualTo("/url?xu=1&y=z&u=2");
}
@Test
public void encodeURLExistingQueryParamEndsWithActualParamMiddle() {
assertThat(strategy.encodeURL("/url?a=b&xu=1&y=z", "2")).isEqualTo("/url?a=b&xu=1&y=z&u=2");
}
@Test
public void encodeURLExistingQueryParamEndsWithActualParamEnd() {
assertThat(strategy.encodeURL("/url?a=b&xu=1", "2")).isEqualTo("/url?a=b&xu=1&u=2");
}
//
@Test
public void encodeURLNoExistingQueryDefaultAlias() {
assertThat(strategy.encodeURL("/url", "0")).isEqualTo("/url");
}
@Test
public void encodeURLNoExistingQueryEmptyDefaultAlias() {
assertThat(strategy.encodeURL("/url?", "0")).isEqualTo("/url?");
}
@Test
public void encodeURLExistingQueryNoAliasDefaultAlias() {
assertThat(strategy.encodeURL("/url?a=b", "0")).isEqualTo("/url?a=b");
}
@Test
public void encodeURLExistingQueryExistingAliasStartDefaultAlias() {
// relaxed constraint as result /url?&y=z does not hurt anything (ideally should remove the &)
assertThat(strategy.encodeURL("/url?u=1&y=z", "0")).doesNotContain("u=0&u=1");
}
@Test
public void encodeURLExistingQueryExistingAliasMiddleDefaultAlias() {
assertThat(strategy.encodeURL("/url?a=b&u=1&y=z", "0")).isEqualTo("/url?a=b&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasEndDefaultAlias() {
assertThat(strategy.encodeURL("/url?a=b&u=1", "0")).isEqualTo("/url?a=b");
}
// --- getCurrentSessionAlias
@Test
public void getCurrentSessionAliasNull() {
assertThat(strategy.getCurrentSessionAlias(request)).isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasNullParamName() {
strategy.setSessionAliasParamName(null);
request.setParameter(CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "NOT USED");
assertThat(strategy.getCurrentSessionAlias(request)).isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSession() {
String expectedAlias = "1";
request.setParameter(CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, expectedAlias);
assertThat(strategy.getCurrentSessionAlias(request)).isEqualTo(expectedAlias);
}
// --- helper
public void setCookieName(String cookieName) {
strategy.setCookieName(cookieName);
this.cookieName = cookieName;
}
public void setSessionId(String id) {
request.setCookies(new Cookie(cookieName, id));
}
public String getSessionId() {
return response.getCookie(cookieName).getValue();
}
}

View File

@@ -2,6 +2,8 @@ package org.springframework.session.web.http;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.Arrays;
@@ -20,15 +22,23 @@ import javax.servlet.http.HttpSessionContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
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.Session;
import org.springframework.session.SessionRepository;
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("deprecation")
public class SessionRepositoryFilterTests<S extends ExpiringSession> {
@Mock
private HttpSessionStrategy strategy;
private SessionRepository<ExpiringSession> sessionRepository;
private SessionRepositoryFilter<ExpiringSession> filter;
@@ -838,6 +848,72 @@ public class SessionRepositoryFilterTests<S extends ExpiringSession> {
});
}
// --- MultiHttpSessionStrategyAdapter
@Test
public void doFilterAdapterGetRequestedSessionId() throws Exception {
filter.setHttpSessionStrategy(strategy);
final String expectedId = "MultiHttpSessionStrategyAdapter-requested-id";
when(strategy.getRequestedSessionId(any(HttpServletRequest.class))).thenReturn(expectedId);
doFilter(new DoInFilter(){
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse) throws IOException {
String actualId = wrappedRequest.getRequestedSessionId();
assertThat(actualId).isEqualTo(expectedId);
}
});
}
@Test
public void doFilterAdapterOnNewSession() throws Exception {
filter.setHttpSessionStrategy(strategy);
doFilter(new DoInFilter(){
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse) throws IOException {
wrappedRequest.getSession();
}
});
HttpServletRequest request = (HttpServletRequest) chain.getRequest();
Session session = sessionRepository.getSession(request.getSession().getId());
verify(strategy).onNewSession(eq(session), any(HttpServletRequest.class),any(HttpServletResponse.class));
}
@Test
public void doFilterAdapterOnInvalidate() throws Exception {
filter.setHttpSessionStrategy(strategy);
doFilter(new DoInFilter(){
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse) throws IOException {
wrappedRequest.getSession().getId();
}
});
HttpServletRequest request = (HttpServletRequest) chain.getRequest();
String id = request.getSession().getId();
when(strategy.getRequestedSessionId(any(HttpServletRequest.class))).thenReturn(id);
doFilter(new DoInFilter(){
@Override
public void doFilter(HttpServletRequest wrappedRequest, HttpServletResponse wrappedResponse) throws IOException {
wrappedRequest.getSession().invalidate();
}
});
verify(strategy).onInvalidateSession(any(HttpServletRequest.class),any(HttpServletResponse.class));
}
// --- order
public void order() {
int expected = 5;
filter.setOrder(expected);
assertThat(filter.getOrder()).isEqualTo(expected);
}
// --- helper methods
private void assertNewSession() {