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:
parent
83bcc6ad7c
commit
8f5bcb64a6
|
@ -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 :
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 =
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue