SEC-689: Session Fixation protection should be available to all authentication mechanisms.

http://jira.springframework.org/browse/SEC-689. Added a general SessionFixationProtectionFilter which can be added to the filter stack to detect when a user has been authenticated and then migrate them to a new session. Also added support to <http/> namespace element.
This commit is contained in:
Luke Taylor 2008-03-25 22:32:26 +00:00
parent 83bcc6ad7c
commit 8f5bcb64a6
5 changed files with 2123 additions and 1011 deletions

View File

@ -20,6 +20,7 @@ import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.util.RedirectUtils;
import org.springframework.security.util.SessionUtils;
import org.springframework.security.context.SecurityContextHolder;
@ -353,9 +354,8 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
logger.debug("Updated SecurityContextHolder to contain the following Authentication: '" + authResult + "'");
}
if (invalidateSessionOnSuccessfulAuthentication) {
startNewSessionIfRequired(request);
SessionUtils.startNewSessionIfRequired(request, migrateInvalidatedSessionAttributes, null);
}
String targetUrl = determineTargetUrl(request);
@ -376,53 +376,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
sendRedirect(request, response, targetUrl);
}
private void startNewSessionIfRequired(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
if (!migrateInvalidatedSessionAttributes) {
if (logger.isDebugEnabled()) {
logger.debug("Invalidating session without migrating attributes.");
}
session.invalidate();
session = null;
// this is probably not necessary, but seems cleaner since
// there already was a session going.
request.getSession(true);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Invalidating session and migrating attributes.");
}
HashMap migratedAttributes = new HashMap();
Enumeration enumer = session.getAttributeNames();
while (enumer.hasMoreElements()) {
String key = (String) enumer.nextElement();
migratedAttributes.put(key, session.getAttribute(key));
}
session.invalidate();
session = request.getSession(true); // we now have a new session
Iterator iter = migratedAttributes.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
session.setAttribute((String) entry.getKey(), entry.getValue());
}
}
}
protected String determineTargetUrl(HttpServletRequest request) {
// Don't attempt to obtain the url from the saved request if alwaysUsedefaultTargetUrl is set
String targetUrl = alwaysUseDefaultTargetUrl ? null :

View File

@ -0,0 +1,156 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationTrustResolver;
import org.springframework.security.AuthenticationTrustResolverImpl;
import org.springframework.security.concurrent.SessionRegistry;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.util.SessionUtils;
/**
* Detects that a user has been authenticated since the start of the request and starts a new session.
* <p>
* This is essentially a generalization of the functionality that was implemented for SEC-399. Additionally, it will
* update the configured SessionRegistry if one is in use, thus preventing problems when used with Spring Security's
* concurrent session control.
*
* @author Martin Algesten
* @author Luke Taylor
* @since 2.0
*/
public class SessionFixationProtectionFilter extends SpringSecurityFilter {
//~ Static fields/initializers =====================================================================================
static final String FILTER_APPLIED = "__spring_security_session_fixation_filter_applied";
//~ Instance fields ================================================================================================
private SessionRegistry sessionRegistry;
/**
* Indicates that the session attributes of the session to be invalidated
* should be migrated to the new session. Defaults to <code>true</code>.
*/
private boolean migrateSessionAttributes = true;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Session fixation isn't a problem if there's no session
if(request.getSession(false) == null || request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (isAuthenticated()) {
// We don't have to worry about session fixation attack if already authenticated
chain.doFilter(request, response);
return;
}
SessionFixationProtectionResponseWrapper wrapper =
new SessionFixationProtectionResponseWrapper(response, request);
try {
chain.doFilter(request, wrapper);
} finally {
if (!wrapper.isNewSessionStarted()) {
startNewSessionIfRequired(request);
}
}
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && !authenticationTrustResolver.isAnonymous(authentication);
}
public void setMigrateSessionAttributes(boolean migrateSessionAttributes) {
this.migrateSessionAttributes = migrateSessionAttributes;
}
public int getOrder() {
return FilterChainOrder.SESSION_FIXATION_FILTER;
}
/**
* Called when the an initially unauthenticated request completes or a redirect or sendError occurs.
* <p>
* If the user is now authenticated, a new session will be created, the session attributes copied to it (if
* <tt>migrateSessionAttributes</tt> is set and the sessionRegistry updated with the new session information.
*/
protected void startNewSessionIfRequired(HttpServletRequest request) {
if (isAuthenticated()) {
SessionUtils.startNewSessionIfRequired(request, migrateSessionAttributes, sessionRegistry);
}
}
/**
* Response wrapper to handle the situation where we need to migrate the session after a redirect or sendError.
* Similar in function to Martin Algesten's OnRedirectUpdateSessionResponseWrapper used in
* HttpSessionContextIntegrationFilter.
*/
private class SessionFixationProtectionResponseWrapper extends HttpServletResponseWrapper {
private HttpServletRequest request;
private boolean newSessionStarted;
public SessionFixationProtectionResponseWrapper(HttpServletResponse response, HttpServletRequest request) {
super(response);
this.request = request;
}
/**
* Makes sure a new session is created before calling the
* superclass <code>sendError()</code>
*/
public void sendError(int sc) throws IOException {
startNewSession();
super.sendError(sc);
}
/**
* Makes sure a new session is created before calling the
* superclass <code>sendError()</code>
*/
public void sendError(int sc, String msg) throws IOException {
startNewSession();
super.sendError(sc, msg);
}
/**
* Makes sure a new session is created before calling the
* superclass <code>sendRedirect()</code>
*/
public void sendRedirect(String location) throws IOException {
startNewSession();
super.sendRedirect(location);
}
/**
* Calls <code>startNewSessionIfRequired()</code>
*/
private void startNewSession() {
if (newSessionStarted) {
return;
}
startNewSessionIfRequired(request);
newSessionStarted = true;
}
private boolean isNewSessionStarted() {
return newSessionStarted;
}
}
}

View File

@ -0,0 +1,75 @@
package org.springframework.security.util;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.concurrent.SessionRegistry;
import org.springframework.security.concurrent.SessionRegistryUtils;
import org.springframework.security.context.SecurityContextHolder;
/**
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public final class SessionUtils {
private final static Log logger = LogFactory.getLog(SessionUtils.class);
SessionUtils() {}
public static void startNewSessionIfRequired(HttpServletRequest request, boolean migrateAttributes,
SessionRegistry sessionRegistry) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
String originalSessionId = session.getId();
if (logger.isDebugEnabled()) {
logger.debug("Invalidating session " + (migrateAttributes ? "and" : "without") + " migrating attributes.");
}
HashMap attributesToMigrate = null;
if (migrateAttributes) {
attributesToMigrate = new HashMap();
Enumeration enumer = session.getAttributeNames();
while (enumer.hasMoreElements()) {
String key = (String) enumer.nextElement();
attributesToMigrate.put(key, session.getAttribute(key));
}
}
session.invalidate();
session = request.getSession(true); // we now have a new session
if (attributesToMigrate != null) {
Iterator iter = attributesToMigrate.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
session.setAttribute((String) entry.getKey(), entry.getValue());
}
}
if (sessionRegistry != null) {
sessionRegistry.removeSessionInformation(originalSessionId);
Object principal = SessionRegistryUtils.obtainPrincipalFromAuthentication(
SecurityContextHolder.getContext().getAuthentication());
sessionRegistry.registerNewSession(session.getId(), principal);
}
}
}

View File

@ -202,6 +202,9 @@ http.attlist &=
http.attlist &=
## Optional attribute specifying the realm name that will be used for all authentication features that require a realm name (eg BASIC and Digest authentication). If unspecified, defaults to "Spring Security Application".
attribute realm {xsd:string}?
http.attlist &=
## Indicates whether an existing session should be invalidated when a user authenticates and a new session started. If set to "none" no change will be made. "newSession" will create a new empty session. "migrateSession" will create a new session and copy the session attributes to the new session. Defaults to "migrateSession".
attribute session-fixation-protection {"none" | "newSession" | "migrateSession" }?
intercept-url =