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.AuthenticationException;
|
||||||
import org.springframework.security.AuthenticationManager;
|
import org.springframework.security.AuthenticationManager;
|
||||||
import org.springframework.security.util.RedirectUtils;
|
import org.springframework.security.util.RedirectUtils;
|
||||||
|
import org.springframework.security.util.SessionUtils;
|
||||||
|
|
||||||
import org.springframework.security.context.SecurityContextHolder;
|
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 + "'");
|
logger.debug("Updated SecurityContextHolder to contain the following Authentication: '" + authResult + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (invalidateSessionOnSuccessfulAuthentication) {
|
if (invalidateSessionOnSuccessfulAuthentication) {
|
||||||
startNewSessionIfRequired(request);
|
SessionUtils.startNewSessionIfRequired(request, migrateInvalidatedSessionAttributes, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
String targetUrl = determineTargetUrl(request);
|
String targetUrl = determineTargetUrl(request);
|
||||||
|
@ -376,53 +376,6 @@ public abstract class AbstractProcessingFilter extends SpringSecurityFilter impl
|
||||||
sendRedirect(request, response, targetUrl);
|
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) {
|
protected String determineTargetUrl(HttpServletRequest request) {
|
||||||
// Don't attempt to obtain the url from the saved request if alwaysUsedefaultTargetUrl is set
|
// Don't attempt to obtain the url from the saved request if alwaysUsedefaultTargetUrl is set
|
||||||
String targetUrl = alwaysUseDefaultTargetUrl ? null :
|
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 &=
|
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".
|
## 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}?
|
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 =
|
intercept-url =
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue