1
0
mirror of synced 2026-08-03 00:37:03 +00:00

SEC-1125: Created separate web module spring-security-web

This commit is contained in:
Luke Taylor
2009-03-25 06:28:18 +00:00
parent 2c985a1c36
commit 2a9a8a41db
247 changed files with 611 additions and 506 deletions
@@ -1,131 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.concurrent;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.ui.logout.LogoutHandler;
import org.springframework.security.ui.logout.SecurityContextLogoutHandler;
import org.springframework.security.util.UrlUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Filter required by concurrent session handling package.
* <p>
* This filter performs two functions. First, it calls
* {@link org.springframework.security.concurrent.SessionRegistry#refreshLastRequest(String)} for each request
* so that registered sessions always have a correct "last update" date/time. Second, it retrieves a
* {@link org.springframework.security.concurrent.SessionInformation} from the <code>SessionRegistry</code>
* for each request and checks if the session has been marked as expired.
* If it has been marked as expired, the configured logout handlers will be called (as happens with
* {@link org.springframework.security.ui.logout.LogoutFilter}), typically to invalidate the session.
* A redirect to the expiredURL specified will be performed, and the session invalidation will cause an
* {@link org.springframework.security.ui.session.HttpSessionDestroyedEvent} to be published via the
* {@link org.springframework.security.ui.session.HttpSessionEventPublisher} registered in <code>web.xml</code>.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class ConcurrentSessionFilter extends SpringSecurityFilter implements InitializingBean {
//~ Instance fields ================================================================================================
private SessionRegistry sessionRegistry;
private String expiredUrl;
private LogoutHandler[] handlers = new LogoutHandler[] {new SecurityContextLogoutHandler()};
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(sessionRegistry, "SessionRegistry required");
Assert.isTrue(expiredUrl == null || UrlUtils.isValidRedirectUrl(expiredUrl),
expiredUrl + " isn't a valid redirect URL");
}
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpSession session = request.getSession(false);
if (session != null) {
SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
if (info != null) {
if (info.isExpired()) {
// Expired - abort processing
doLogout(request, response);
String targetUrl = determineExpiredUrl(request, info);
if (targetUrl != null) {
targetUrl = request.getContextPath() + targetUrl;
response.sendRedirect(response.encodeRedirectURL(targetUrl));
} else {
response.getWriter().print("This session has been expired (possibly due to multiple concurrent " +
"logins being attempted as the same user).");
response.flushBuffer();
}
return;
} else {
// Non-expired - update last request date/time
info.refreshLastRequest();
}
}
}
chain.doFilter(request, response);
}
protected String determineExpiredUrl(HttpServletRequest request, SessionInformation info) {
return expiredUrl;
}
private void doLogout(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
for (int i = 0; i < handlers.length; i++) {
handlers[i].logout(request, response, auth);
}
}
public void setExpiredUrl(String expiredUrl) {
this.expiredUrl = expiredUrl;
}
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
public void setLogoutHandlers(LogoutHandler[] handlers) {
Assert.notNull(handlers);
this.handlers = handlers;
}
public int getOrder() {
return FilterChainOrder.CONCURRENT_SESSION_FILTER;
}
}
@@ -23,12 +23,14 @@ import java.io.Serializable;
/**
* Represents a record of a session within the Spring Security framework.
* <p>This is primarily used for concurrent session support.</p>
* <p>Sessions have three states: active, expired, and destroyed. A session can that is invalidated by
* <p>
* This is primarily used for concurrent session support.
* <p>
* Sessions have three states: active, expired, and destroyed. A session can that is invalidated by
* <code>session.invalidate()</code> or via Servlet Container management is considered "destroyed". An "expired"
* session, on the other hand, is a session that Spring Security wants to end because it was selected for removal for
* some reason (generally as it was the least recently used session and the maximum sessions for the user were
* reached). An "expired" session is removed as soon as possible by a <code>Filter</code>.</p>
* reached). An "expired" session is removed as soon as possible by a <code>Filter</code>.
*
* @author Ben Alex
* @version $Id$
@@ -1,177 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.concurrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.security.ui.session.HttpSessionDestroyedEvent;
import org.springframework.util.Assert;
/**
* Base implementation of {@link org.springframework.security.concurrent.SessionRegistry}
* which also listens for {@link org.springframework.security.ui.session.HttpSessionDestroyedEvent}s
* published in the Spring application context.
*
* <p>
* NB: It is important that you register the {@link org.springframework.security.ui.session.HttpSessionEventPublisher} in
* <code>web.xml</code> so that this class is notified of sessions that expire.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class SessionRegistryImpl implements SessionRegistry, ApplicationListener {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(SessionRegistryImpl.class);
// ~ Instance fields ===============================================================================================
/** <principal:Object,SessionIdSet> */
private Map<Object,Set<String>> principals = Collections.synchronizedMap(new HashMap<Object,Set<String>>());
/** <sessionId:Object,SessionInformation> */
private Map<String, SessionInformation> sessionIds = Collections.synchronizedMap(new HashMap<String, SessionInformation>());
// ~ Methods =======================================================================================================
public Object[] getAllPrincipals() {
return principals.keySet().toArray();
}
public SessionInformation[] getAllSessions(Object principal, boolean includeExpiredSessions) {
Set<String> sessionsUsedByPrincipal = principals.get(principal);
if (sessionsUsedByPrincipal == null) {
return null;
}
List<SessionInformation> list = new ArrayList<SessionInformation>();
synchronized (sessionsUsedByPrincipal) {
for (String sessionId : sessionsUsedByPrincipal) {
SessionInformation sessionInformation = getSessionInformation(sessionId);
if (sessionInformation == null) {
continue;
}
if (includeExpiredSessions || !sessionInformation.isExpired()) {
list.add(sessionInformation);
}
}
}
return (SessionInformation[]) list.toArray(new SessionInformation[0]);
}
public SessionInformation getSessionInformation(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
return (SessionInformation) sessionIds.get(sessionId);
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof HttpSessionDestroyedEvent) {
String sessionId = ((HttpSession) event.getSource()).getId();
removeSessionInformation(sessionId);
}
}
public void refreshLastRequest(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
SessionInformation info = getSessionInformation(sessionId);
if (info != null) {
info.refreshLastRequest();
}
}
public synchronized void registerNewSession(String sessionId, Object principal) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
Assert.notNull(principal, "Principal required as per interface contract");
if (logger.isDebugEnabled()) {
logger.debug("Registering session " + sessionId +", for principal " + principal);
}
if (getSessionInformation(sessionId) != null) {
removeSessionInformation(sessionId);
}
sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
Set<String> sessionsUsedByPrincipal = principals.get(principal);
if (sessionsUsedByPrincipal == null) {
sessionsUsedByPrincipal = Collections.synchronizedSet(new HashSet<String>(4));
principals.put(principal, sessionsUsedByPrincipal);
}
sessionsUsedByPrincipal.add(sessionId);
}
public void removeSessionInformation(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
SessionInformation info = getSessionInformation(sessionId);
if (info == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Removing session " + sessionId + " from set of registered sessions");
}
sessionIds.remove(sessionId);
Set<String> sessionsUsedByPrincipal = principals.get(info.getPrincipal());
if (sessionsUsedByPrincipal == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Removing session " + sessionId + " from principal's set of registered sessions");
}
synchronized (sessionsUsedByPrincipal) {
sessionsUsedByPrincipal.remove(sessionId);
if (sessionsUsedByPrincipal.size() == 0) {
// No need to keep object in principals Map anymore
if (logger.isDebugEnabled()) {
logger.debug("Removing principal " + info.getPrincipal() + " from registry");
}
principals.remove(info.getPrincipal());
}
}
}
}
@@ -1,39 +0,0 @@
package org.springframework.security.context;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Used to pass the incoming request to {@link SecurityContextRepository#loadContext(HttpRequestResponseHolder)},
* allowing the method to swap the request for a wrapped version, as well as returning the <tt>SecurityContext</tt>
* value.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class HttpRequestResponseHolder {
HttpServletRequest request;
HttpServletResponse response;
public HttpRequestResponseHolder(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
HttpServletRequest getRequest() {
return request;
}
void setRequest(HttpServletRequest request) {
this.request = request;
}
HttpServletResponse getResponse() {
return response;
}
void setResponse(HttpServletResponse response) {
this.response = response;
}
}
@@ -1,199 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.context;
import javax.servlet.ServletException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.ui.FilterChainOrder;
/**
* Populates the {@link SecurityContextHolder} with information obtained from
* the <code>HttpSession</code>.
* <p/>
* <p/>
* The <code>HttpSession</code> will be queried to retrieve the
* <code>SecurityContext</code> that should be stored against the
* <code>SecurityContextHolder</code> for the duration of the web request. At
* the end of the web request, any updates made to the
* <code>SecurityContextHolder</code> will be persisted back to the
* <code>HttpSession</code> by this filter.
* </p>
* <p/>
* If a valid <code>SecurityContext</code> cannot be obtained from the
* <code>HttpSession</code> for whatever reason, a fresh
* <code>SecurityContext</code> will be created and used instead. The created
* object will be of the instance defined by the {@link #setContextClass(Class)}
* method (which defaults to {@link org.springframework.security.context.SecurityContextImpl}.
* </p>
* <p/>
* No <code>HttpSession</code> will be created by this filter if one does not
* already exist. If at the end of the web request the <code>HttpSession</code>
* does not exist, a <code>HttpSession</code> will <b>only</b> be created if
* the current contents of the <code>SecurityContextHolder</code> are not
* {@link java.lang.Object#equals(java.lang.Object)} to a <code>new</code>
* instance of {@link #setContextClass(Class)}. This avoids needless
* <code>HttpSession</code> creation, but automates the storage of changes
* made to the <code>SecurityContextHolder</code>. There is one exception to
* this rule, that is if the {@link #forceEagerSessionCreation} property is
* <code>true</code>, in which case sessions will always be created
* irrespective of normal session-minimisation logic (the default is
* <code>false</code>, as this is resource intensive and not recommended).
* </p>
* <p/>
* This filter will only execute once per request, to resolve servlet container
* (specifically Weblogic) incompatibilities.
* </p>
* <p/>
* If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
* created (eg this filter is only being used with Basic authentication or
* similar clients that will never present the same <code>jsessionid</code>
* etc), the {@link #setAllowSessionCreation(boolean)} should be set to
* <code>false</code>. Only do this if you really need to conserve server
* memory and ensure all classes using the <code>SecurityContextHolder</code>
* are designed to have no persistence of the <code>SecurityContext</code>
* between web requests. Please note that if {@link #forceEagerSessionCreation}
* is <code>true</code>, the <code>allowSessionCreation</code> must also be
* <code>true</code> (setting it to <code>false</code> will cause a startup
* time error).
* </p>
* <p/>
* This filter MUST be executed BEFORE any authentication processing mechanisms.
* Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
* expect the <code>SecurityContextHolder</code> to contain a valid
* <code>SecurityContext</code> by the time they execute.
* </p>
*
* @author Ben Alex
* @author Patrick Burleson
* @author Luke Taylor
* @author Martin Algesten
*
* @deprecated Use SecurityContextPersistenceFilter instead.
*
* @version $Id$
*/
public class HttpSessionContextIntegrationFilter extends SecurityContextPersistenceFilter implements InitializingBean {
//~ Static fields/initializers =====================================================================================
public static final String SPRING_SECURITY_CONTEXT_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
//~ Instance fields ================================================================================================
private Class<? extends SecurityContext> contextClass = SecurityContextImpl.class;
/**
* Indicates if this filter can create a <code>HttpSession</code> if
* needed (sessions are always created sparingly, but setting this value to
* <code>false</code> will prohibit sessions from ever being created).
* Defaults to <code>true</code>. Do not set to <code>false</code> if
* you are have set {@link #forceEagerSessionCreation} to <code>true</code>,
* as the properties would be in conflict.
*/
private boolean allowSessionCreation = true;
/**
* Indicates if this filter is required to create a <code>HttpSession</code>
* for every request before proceeding through the filter chain, even if the
* <code>HttpSession</code> would not ordinarily have been created. By
* default this is <code>false</code>, which is entirely appropriate for
* most circumstances as you do not want a <code>HttpSession</code>
* created unless the filter actually needs one. It is envisaged the main
* situation in which this property would be set to <code>true</code> is
* if using other filters that depend on a <code>HttpSession</code>
* already existing, such as those which need to obtain a session ID. This
* is only required in specialised cases, so leave it set to
* <code>false</code> unless you have an actual requirement and are
* conscious of the session creation overhead.
*/
private boolean forceEagerSessionCreation = false;
/**
* Indicates whether the <code>SecurityContext</code> will be cloned from
* the <code>HttpSession</code>. The default is to simply reference (ie
* the default is <code>false</code>). The default may cause issues if
* concurrent threads need to have a different security identity from other
* threads being concurrently processed that share the same
* <code>HttpSession</code>. In most normal environments this does not
* represent an issue, as changes to the security identity in one thread is
* allowed to affect the security identitiy in other threads associated with
* the same <code>HttpSession</code>. For unusual cases where this is not
* permitted, change this value to <code>true</code> and ensure the
* {@link #contextClass} is set to a <code>SecurityContext</code> that
* implements {@link Cloneable} and overrides the <code>clone()</code>
* method.
*/
private boolean cloneFromHttpSession = false;
// private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
public HttpSessionContextIntegrationFilter() throws ServletException {
super.setSecurityContextRepository(repo);
}
public boolean isCloneFromHttpSession() {
return cloneFromHttpSession;
}
public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
this.cloneFromHttpSession = cloneFromHttpSession;
repo.setCloneFromHttpSession(cloneFromHttpSession);
}
public boolean isAllowSessionCreation() {
return allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
repo.setAllowSessionCreation(allowSessionCreation);
}
protected Class<? extends SecurityContext> getContextClass() {
return contextClass;
}
@SuppressWarnings("unchecked")
public void setContextClass(Class secureContext) {
this.contextClass = secureContext;
repo.setSecurityContextClass(secureContext);
}
public boolean isForceEagerSessionCreation() {
return forceEagerSessionCreation;
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
super.setForceEagerSessionCreation(forceEagerSessionCreation);
}
public int getOrder() {
return FilterChainOrder.HTTP_SESSION_CONTEXT_FILTER;
}
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
if (forceEagerSessionCreation && !allowSessionCreation) {
throw new IllegalArgumentException(
"If using forceEagerSessionCreation, you must set allowSessionCreation to also be true");
}
}
}
@@ -1,369 +0,0 @@
package org.springframework.security.context;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.AuthenticationTrustResolver;
import org.springframework.security.AuthenticationTrustResolverImpl;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* A <tt>SecurityContextRepository</tt> implementation which stores the security context in the HttpSession between
* requests.
* <p>
* The <code>HttpSession</code> will be queried to retrieve the <code>SecurityContext</code> in the <tt>loadContext</tt>
* method (using the key {@link #SPRING_SECURITY_CONTEXT_KEY}). If a valid <code>SecurityContext</code> cannot be
* obtained from the <code>HttpSession</code> for whatever reason, a fresh <code>SecurityContext</code> will be created
* and returned instead. The created object will be an instance of the class set using the
* {@link #setSecurityContextClass(Class)} method. If this hasn't been set, a {@link SecurityContextImpl} will be returned.
* <p>
* When <tt>saveContext</tt> is called, the context will be stored under the same key, provided
* <ol>
* <li>The value has changed</li>
* <li>The configured <tt>AuthenticationTrustResolver</tt> does not report that the contents represent an anonymous
* user</li>
* </ol>
* <p>
* With the standard configuration, no <code>HttpSession</code> will be created during <tt>loadContext</tt> if one does
* not already exist. When <tt>saveContext</tt> is called at the end of the web request, and no session exists, a new
* <code>HttpSession</code> will <b>only</b> be created if the supplied <tt>SecurityContext</tt> is not equal
* to a <code>new</code> instance of the {@link #setContextClass(Class) contextClass} (or an empty
* <tt>SecurityContextImpl</tt> if the class has not been set. This avoids needless <code>HttpSession</code> creation,
* but automates the storage of changes made to the context during the request. Note that if
* {@link SecurityContextPersistenceFilter} is configured to eagerly create sessions, then the session-minimisation
* logic applied here will not make any difference. If you are using eager session creation, then you should
* ensure that the <tt>allowSessionCreation</tt> property of this class is set to <tt>true</tt> (the default).
* <p>
* If for whatever reason no <code>HttpSession</code> should <b>ever</b> be created (e.g. Basic authentication is being
* used or similar clients that will never present the same <code>jsessionid</code> etc), then
* {@link #setAllowSessionCreation(boolean) allowSessionCreation} should be set to <code>false</code>.
* Only do this if you really need to conserve server memory and ensure all classes using the
* <code>SecurityContextHolder</code> are designed to have no persistence of the <code>SecurityContext</code>
* between web requests.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
protected final Log logger = LogFactory.getLog(this.getClass());
private Class<? extends SecurityContext> securityContextClass = null;
/** SecurityContext instance used to check for equality with default (unauthenticated) content */
private Object contextObject = new SecurityContextImpl();
private boolean cloneFromHttpSession = false;
private boolean allowSessionCreation = true;
private boolean disableUrlRewriting = false;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
/**
* Gets the security context from the session (if available) and returns it.
* <p>
* If the session is null, the context object is null or the context object stored in the session
* is not an instance of <tt>SecurityContext</tt>, a new context object will be generated and
* returned.
* <p>
* If <tt>cloneFromHttpSession</tt> is set to true, it will attempt to clone the context object first
* and return the cloned instance.
*/
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: " + httpSession +". " +
"A new one will be created.");
}
context = generateNewContext();
}
requestResponseHolder.setResponse(new SaveToSessionResponseWrapper(response, request,
httpSession != null, context.hashCode()));
return context;
}
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
SaveToSessionResponseWrapper responseWrapper = (SaveToSessionResponseWrapper)response;
// saveContext() might already be called by the response wrapper
// if something in the chain called sendError() or sendRedirect(). This ensures we only call it
// once per request.
if (!responseWrapper.isContextSaved() ) {
responseWrapper.saveContext(context);
}
}
/**
*
* @param httpSession the session obtained from the request.
*/
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
if (httpSession == null) {
if (logger.isDebugEnabled()) {
logger.debug("No HttpSession currently exists");
}
return null;
}
// Session exists, so try to obtain a context from it.
Object contextFromSession = httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY);
if (contextFromSession == null) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}
return null;
}
// We now have the security context object from the session.
if (!(contextFromSession instanceof SecurityContext)) {
if (logger.isWarnEnabled()) {
logger.warn("SPRING_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
+ contextFromSession + "'; are you improperly modifying the HttpSession directly "
+ "(you should always use SecurityContextHolder) or using the HttpSession attribute "
+ "reserved for this class?");
}
return null;
}
// Clone if required (see SEC-356)
if (cloneFromHttpSession) {
contextFromSession = cloneContext(contextFromSession);
}
if (logger.isDebugEnabled()) {
logger.debug("Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: '" + contextFromSession + "'");
}
// Everything OK. The only non-null return from this method.
return (SecurityContext) contextFromSession;
}
/**
*
* @param context the object which was stored under the security context key in the HttpSession.
* @return the cloned SecurityContext object. Never null.
*/
private Object cloneContext(Object context) {
Object clonedContext = null;
Assert.isInstanceOf(Cloneable.class, context,
"Context must implement Cloneable and provide a Object.clone() method");
try {
Method m = context.getClass().getMethod("clone", new Class[]{});
if (!m.isAccessible()) {
m.setAccessible(true);
}
clonedContext = m.invoke(context, new Object[]{});
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
return clonedContext;
}
/**
* By default, calls {@link SecurityContextHolder#createEmptyContext()} to obtain a new context (there should be
* no context present in the holder when this method is called). Using this approach the context creation
* strategy is decided by the {@link SecurityContextHolderStrategy} in use. The default implementations
* will return a new <tt>SecurityContextImpl</tt>.
* <p>
* An alternative way of customizing the <tt>SecurityContext</tt> implementation is by setting the
* <tt>securityContextClass</tt> property. In this case, the method will attempt to invoke the no-args
* constructor on the supplied class instead and return the created instance.
*
* @return a new SecurityContext instance. Never null.
*/
SecurityContext generateNewContext() {
SecurityContext context = null;
if (securityContextClass == null) {
context = SecurityContextHolder.createEmptyContext();
return context;
}
try {
context = securityContextClass.newInstance();
} catch (Exception e) {
ReflectionUtils.handleReflectionException(e);
}
return context;
}
@SuppressWarnings("unchecked")
public void setSecurityContextClass(Class contextClass) {
if (contextClass == null || (!SecurityContext.class.isAssignableFrom(contextClass))) {
throw new IllegalArgumentException("securityContextClass must implement SecurityContext "
+ "(typically use org.springframework.security.context.SecurityContextImpl; existing class is "
+ contextClass + ")");
}
this.securityContextClass = contextClass;
contextObject = generateNewContext();
}
public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
this.cloneFromHttpSession = cloneFromHttpSession;
}
/**
* If set to true (the default), a new session will be created if to store the security context if it is determined
* that it's contents are different from the default.
*
* @param allowSessionCreation
*/
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
/**
* Allows the use of session identifiers in URLs to be disabled. Off by default.
*
* @param disableUrlRewriting set to <tt>true</tt> to disable URL encoding methods in the response wrapper
* and prevent the use of <tt>jsessionid</tt> parameters.
*/
public void setDisableUrlRewriting(boolean disableUrlRewriting) {
this.disableUrlRewriting = disableUrlRewriting;
}
//~ Inner Classes ==================================================================================================
/**
* Wrapper that is applied to every request/response to update the <code>HttpSession<code> with
* the <code>SecurityContext</code> when a <code>sendError()</code> or <code>sendRedirect</code>
* happens. See SEC-398.
* <p>
* Stores the necessary state from the start of the request in order to make a decision about whether
* the security context has changed before saving it.
*/
class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {
private HttpServletRequest request;
private boolean httpSessionExistedAtStartOfRequest;
private int contextHashBeforeChainExecution;
/**
* Takes the parameters required to call <code>saveContext()</code> successfully in
* addition to the request and the response object we are wrapping.
*
* @param request the request object (used to obtain the session, if one exists).
* @param httpSessionExistedAtStartOfRequest indicates whether there was a session in place before the
* filter chain executed. If this is true, and the session is found to be null, this indicates that it was
* invalidated during the request and a new session will now be created.
* @param contextHashBeforeChainExecution the hashcode of the context before the filter chain executed.
* The context will only be stored if it has a different hashcode, indicating that the context changed
* during the request.
*/
SaveToSessionResponseWrapper(HttpServletResponse response, HttpServletRequest request,
boolean httpSessionExistedAtStartOfRequest,
int contextHashBeforeChainExecution) {
super(response, disableUrlRewriting);
this.request = request;
this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
this.contextHashBeforeChainExecution = contextHashBeforeChainExecution;
}
/**
* Stores the supplied security context in the session (if available) and if it has changed since it was
* set at the start of the request. If the AuthenticationTrustResolver identifies the current user as
* anonymous, then the context will not be stored.
*
* @param context the context object obtained from the SecurityContextHolder after the request has
* been processed by the filter chain. SecurityContextHolder.getContext() cannot be used to obtain
* the context as it has already been cleared by the time this method is called.
*
*/
@Override
void saveContext(SecurityContext context) {
HttpSession httpSession = request.getSession(false);
if (httpSession == null) {
httpSession = createNewSessionIfAllowed(context);
}
// If HttpSession exists, store current SecurityContextHolder contents but only if
// the SecurityContext has actually changed (see JIRA SEC-37)
if (httpSession != null && context.hashCode() != contextHashBeforeChainExecution) {
// See SEC-776
// TODO: Move this so that a session isn't created if user is anonymous
if (authenticationTrustResolver.isAnonymous(context.getAuthentication())) {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContext contents are anonymous - context will not be stored in HttpSession. ");
}
} else {
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContext stored to HttpSession: '" + context + "'");
}
}
}
}
private HttpSession createNewSessionIfAllowed(SecurityContext context) {
if (httpSessionExistedAtStartOfRequest) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession is now null, but was not null at start of request; "
+ "session was invalidated, so do not create a new session");
}
return null;
}
if (!allowSessionCreation) {
if (logger.isDebugEnabled()) {
logger.debug("The HttpSession is currently null, and the "
+ "HttpSessionContextIntegrationFilter is prohibited from creating an HttpSession "
+ "(because the allowSessionCreation property is false) - SecurityContext thus not "
+ "stored for next request");
}
return null;
}
// Generate a HttpSession only if we need to
if (contextObject.equals(context)) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession is null, but SecurityContext has not changed from default: ' "
+ context
+ "'; not creating HttpSession or storing SecurityContext");
}
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("HttpSession being created as SecurityContext is non-default");
}
try {
return request.getSession(true);
} catch (IllegalStateException e) {
// Response must already be committed, therefore can't create a new session
logger.warn("Failed to create a session, as response has been committed. Unable to store" +
" SecurityContext.");
}
return null;
}
}
}
@@ -26,7 +26,7 @@ import org.springframework.util.Assert;
* @version $Id$
*
* @see java.lang.ThreadLocal
* @see org.springframework.security.context.SecurityContextPersistenceFilter
* @see org.springframework.security.context.web.SecurityContextPersistenceFilter
*/
public class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
//~ Static fields/initializers =====================================================================================
@@ -1,122 +0,0 @@
package org.springframework.security.context;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* Base class for response wrappers which encapsulate the logic for storing a security context and which
* store the with the <code>SecurityContext</code> when a <code>sendError()</code> or <code>sendRedirect</code>
* happens. See issue SEC-398.
* <p>
* Sub-classes should implement the {@link #saveContext(SecurityContext context)} method.
* <p>
* Support is also provided for disabling URL rewriting
*
* @author Luke Taylor
* @author Marten Algesten
* @version $Id$
* @since 2.5
*/
public abstract class SaveContextOnUpdateOrErrorResponseWrapper extends HttpServletResponseWrapper {
private boolean contextSaved = false;
/* See SEC-1052 */
private boolean disableUrlRewriting;
/**
* @param response the response to be wrapped
* @param disableUrlRewriting turns the URL encoding methods into null operations, preventing the use
* of URL rewriting to add the session identifier as a URL parameter.
*/
public SaveContextOnUpdateOrErrorResponseWrapper(HttpServletResponse response, boolean disableUrlRewriting) {
super(response);
this.disableUrlRewriting = disableUrlRewriting;
}
/**
* Implements the logic for storing the security context.
*
* @param context the <tt>SecurityContext</tt> instance to store
*/
abstract void saveContext(SecurityContext context);
/**
* Makes sure the session is updated before calling the
* superclass <code>sendError()</code>
*/
@Override
public final void sendError(int sc) throws IOException {
doSaveContext();
super.sendError(sc);
}
/**
* Makes sure the session is updated before calling the
* superclass <code>sendError()</code>
*/
@Override
public final void sendError(int sc, String msg) throws IOException {
doSaveContext();
super.sendError(sc, msg);
}
/**
* Makes sure the context is stored before calling the
* superclass <code>sendRedirect()</code>
*/
@Override
public final void sendRedirect(String location) throws IOException {
doSaveContext();
super.sendRedirect(location);
}
/**
* Calls <code>saveContext()</code> with the current contents of the <tt>SecurityContextHolder</tt>.
*/
private void doSaveContext() {
saveContext(SecurityContextHolder.getContext());
contextSaved = true;
}
@Override
public final String encodeRedirectUrl(String url) {
if (disableUrlRewriting) {
return url;
}
return super.encodeRedirectUrl(url);
}
@Override
public final String encodeRedirectURL(String url) {
if (disableUrlRewriting) {
return url;
}
return super.encodeRedirectURL(url);
}
@Override
public final String encodeUrl(String url) {
if (disableUrlRewriting) {
return url;
}
return super.encodeUrl(url);
}
@Override
public final String encodeURL(String url) {
if (disableUrlRewriting) {
return url;
}
return super.encodeURL(url);
}
/**
* Tells if the response wrapper has called <code>saveContext()</code> because of an error or redirect.
*/
public final boolean isContextSaved() {
return contextSaved;
}
}
@@ -43,7 +43,7 @@ import java.lang.reflect.Constructor;
* @author Ben Alex
* @version $Id$
*
* @see org.springframework.security.context.HttpSessionContextIntegrationFilter
* @see org.springframework.security.context.web.HttpSessionContextIntegrationFilter
*/
public class SecurityContextHolder {
//~ Static fields/initializers =====================================================================================
@@ -138,7 +138,7 @@ public class SecurityContextHolder {
/**
* Delegates the creation of a new, empty context to the configured strategy.
*/
static SecurityContext createEmptyContext() {
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
@@ -1,95 +0,0 @@
package org.springframework.security.context;
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.HttpSession;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
/**
* Populates the {@link SecurityContextHolder} with information obtained from
* the configured {@link SecurityContextRepository} prior to the request and stores it back in the repository
* once the request has completed. By default it uses an {@link HttpSessionSecurityContextRepository}. See this
* class for information <tt>HttpSession</tt> related configuration options.
* <p>
* This filter will only execute once per request, to resolve servlet container (specifically Weblogic)
* incompatibilities.
* <p>
* This filter MUST be executed BEFORE any authentication processing mechanisms. Authentication processing mechanisms
* (e.g. BASIC, CAS processing filters etc) expect the <code>SecurityContextHolder</code> to contain a valid
* <code>SecurityContext</code> by the time they execute.
* <p>
* This is essentially a refactoring of the old <tt>HttpSessionContextIntegrationFilter</tt> to delegate
* the storage issues to a separate strategy, allowing for more customization in the way the security context is
* maintained between requests.
* <p>
* The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is always available before
* the filter chain executes (the default is <code>false</code>, as this is resource intensive and not recommended).
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class SecurityContextPersistenceFilter extends SpringSecurityFilter {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo = new HttpSessionSecurityContextRepository();
private boolean forceEagerSessionCreation = false;
@Override
protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
logger.debug("Eagerly created session: " + session.getId());
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
public void setSecurityContextRepository(SecurityContextRepository repo) {
this.repo = repo;
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
public int getOrder() {
return FilterChainOrder.SECURITY_CONTEXT_FILTER;
}
}
@@ -1,51 +0,0 @@
package org.springframework.security.context;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Strategy used for persisting a {@link SecurityContext} between requests.
* <p>
* Used by {@link SecurityContextPersistenceFilter} to obtain the context which should be used for the current thread
* of execution and to store the context once it has been removed from thread-local storage and the request has
* completed.
* <p>
* The persistence mechanism used will depend on the implementation, but most commonly the <tt>HttpSession</tt> will
* be used to store the context.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*
* @see SecurityContextPersistenceFilter
* @see HttpSessionSecurityContextRepository
* @see SaveContextOnUpdateOrErrorResponseWrapper
*/
public interface SecurityContextRepository {
/**
* Obtains the security context for the supplied request. For an unauthenticated user, an empty context
* implementation should be returned. This method should not return null.
* <p>
* The use of the <tt>HttpRequestResponseHolder</tt> parameter allows implementations to return wrapped versions of
* the request or response (or both), allowing them to access implementation-specific state for the request.
* The values obtained from the holder will be passed on to the filter chain and also to the <tt>saveContext</tt>
* method when it is finally called. Implementations may wish to return a subclass of
* {@link SaveContextOnUpdateOrErrorResponseWrapper} as the response object, which guarantees that the context is
* persisted when an error or redirect occurs.
*
* @param requestResponseHolder holder for the current request and response for which the context should be loaded.
*
* @return The security context which should be used for the current request, never null.
*/
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
/**
* Stores the security context on completion of a request.
*
* @param context the non-null context which was obtained f
* @param request
* @param response
*/
void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);
}
@@ -25,7 +25,7 @@ import org.springframework.util.Assert;
* @version $Id$
*
* @see java.lang.ThreadLocal
* @see org.springframework.security.context.SecurityContextPersistenceFilter
* @see org.springframework.security.context.web.SecurityContextPersistenceFilter
*/
public class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
//~ Static fields/initializers =====================================================================================
@@ -1,8 +1,7 @@
<html>
<body>
Classes related to the establishment of a security context for the duration of a request (such as
an HTTP or RMI invocation) and for the maintenance of the context between requests (by storing it in an HTTP sessio, for
example).
an HTTP or RMI invocation).
<p>
A security context is associated with the current execution thread for the duration of the request, making the
authentication information it contains available throughout all the layers of an application.
@@ -0,0 +1,18 @@
package org.springframework.security.event;
import org.springframework.context.ApplicationEvent;
/**
* Generic session creation event which indicates that a session (potentially
* represented by a security context) has begun.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public abstract class SessionCreationEvent extends ApplicationEvent {
public SessionCreationEvent(Object source) {
super(source);
}
}
@@ -0,0 +1,26 @@
package org.springframework.security.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.context.SecurityContext;
/**
* Generic "session termination" event which indicates that a session (potentially
* represented by a security context) has ended.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public abstract class SessionDestroyedEvent extends ApplicationEvent {
public SessionDestroyedEvent(Object source) {
super(source);
}
/**
* Provides the <tt>SecurityContext</tt> under which the session was running.
*
* @return the <tt>SecurityContext</tt> associated with the session, or null if there is no context.
*/
public abstract SecurityContext getSecurityContext();
}
@@ -5,17 +5,16 @@ import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.Authentication;
import org.springframework.security.intercept.web.FilterInvocation;
/**
* Facade which isolates Spring Security's requirements from the implementation of the underlying
* expression objects.
* Facade which isolates Spring Security's requirements for evaluation method-security expressions
* from the implementation of the underlying expression objects.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public interface SecurityExpressionHandler {
public interface MethodSecurityExpressionHandler {
/**
* @return an expression parser for the expressions used by the implementation.
*/
@@ -26,11 +25,6 @@ public interface SecurityExpressionHandler {
*/
EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation mi);
/**
* Provides an evaluation context in which to evaluate security expressions for a web invocation.
*/
EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi);
/**
* Filters a target collection or array.
* Only applies to method invocations.
@@ -12,7 +12,7 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.expression.MethodSecurityExpressionHandler;
import org.springframework.security.expression.annotation.PostAuthorize;
import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize;
@@ -45,10 +45,10 @@ public class ExpressionAnnotationMethodSecurityMetadataSource extends AbstractMe
}
/**
* Constructor which obtains the expression parser from the {@link SecurityExpressionHandler#getExpressionParser() }
* Constructor which obtains the expression parser from the {@link MethodSecurityExpressionHandler#getExpressionParser() }
* method on the supplied <tt>SecurityExpressionHandler</tt>.
*/
public ExpressionAnnotationMethodSecurityMetadataSource(SecurityExpressionHandler handler) {
public ExpressionAnnotationMethodSecurityMetadataSource(MethodSecurityExpressionHandler handler) {
parser = handler.getExpressionParser();
}
@@ -12,8 +12,8 @@ import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.afterinvocation.AfterInvocationProvider;
import org.springframework.security.expression.ExpressionUtils;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.expression.support.DefaultSecurityExpressionHandler;
import org.springframework.security.expression.MethodSecurityExpressionHandler;
import org.springframework.security.expression.support.DefaultMethodSecurityExpressionHandler;
/**
* AfterInvocationProvider which handles the @PostAuthorize and @PostFilter annotation expressions.
@@ -26,7 +26,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP
protected final Log logger = LogFactory.getLog(getClass());
private SecurityExpressionHandler expressionHandler = new DefaultSecurityExpressionHandler();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
public Object decide(Authentication authentication, Object object, List<ConfigAttribute> config, Object returnedObject)
throws AccessDeniedException {
@@ -90,7 +90,7 @@ public class MethodExpressionAfterInvocationProvider implements AfterInvocationP
return clazz.isAssignableFrom(MethodInvocation.class);
}
public void setExpressionHandler(SecurityExpressionHandler expressionHandler) {
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -11,8 +11,8 @@ import org.springframework.expression.Expression;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.ExpressionUtils;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.expression.support.DefaultSecurityExpressionHandler;
import org.springframework.security.expression.MethodSecurityExpressionHandler;
import org.springframework.security.expression.support.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.vote.AccessDecisionVoter;
/**
@@ -29,7 +29,7 @@ import org.springframework.security.vote.AccessDecisionVoter;
public class MethodExpressionVoter implements AccessDecisionVoter {
protected final Log logger = LogFactory.getLog(getClass());
private SecurityExpressionHandler expressionHandler = new DefaultSecurityExpressionHandler();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof AbstractExpressionBasedMethodConfigAttribute;
@@ -105,7 +105,7 @@ public class MethodExpressionVoter implements AccessDecisionVoter {
return null;
}
public void setExpressionHandler(SecurityExpressionHandler expressionHandler) {
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -14,14 +14,12 @@ import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationTrustResolver;
import org.springframework.security.AuthenticationTrustResolverImpl;
import org.springframework.security.expression.ExpressionUtils;
import org.springframework.security.expression.PermissionEvaluator;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.expression.MethodSecurityExpressionHandler;
/**
* The standard implementation of <tt>SecurityExpressionHandler</tt>.
@@ -32,7 +30,7 @@ import org.springframework.security.intercept.web.FilterInvocation;
* @version $Id$
* @since 2.5
*/
public class DefaultSecurityExpressionHandler implements SecurityExpressionHandler {
public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
protected final Log logger = LogFactory.getLog(getClass());
@@ -41,7 +39,7 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private ExpressionParser expressionParser = new SpelAntlrExpressionParser();
public DefaultSecurityExpressionHandler() {
public DefaultMethodSecurityExpressionHandler() {
}
/**
@@ -58,15 +56,6 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
return ctx;
}
public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) {
StandardEvaluationContext ctx = new StandardEvaluationContext();
SecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
root.setTrustResolver(trustResolver);
ctx.setRootObject(root);
return ctx;
}
@SuppressWarnings("unchecked")
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject();
@@ -15,7 +15,7 @@ import org.springframework.security.util.AuthorityUtils;
* @version $Id$
* @since 2.5
*/
abstract class SecurityExpressionRoot {
public abstract class SecurityExpressionRoot {
protected final Authentication authentication;
private AuthenticationTrustResolver trustResolver;
/** Allows "permitAll" expression */
@@ -24,7 +24,7 @@ abstract class SecurityExpressionRoot {
/** Allows "denyAll" expression */
public final boolean denyAll = false;
SecurityExpressionRoot(Authentication a) {
public SecurityExpressionRoot(Authentication a) {
if (a == null) {
throw new IllegalArgumentException("Authentication object cannot be null");
}
@@ -1,85 +0,0 @@
package org.springframework.security.expression.support;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import org.springframework.security.Authentication;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.util.StringUtils;
/**
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
class WebSecurityExpressionRoot extends SecurityExpressionRoot {
private FilterInvocation filterInvocation;
WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
super(a);
this.filterInvocation = fi;
}
/**
* Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or 202.24.0.0/14).
*
* @param ipAddress the address or range of addresses from which the request must come.
* @return true if the IP address of the current request is in the required range.
*/
public boolean hasIpAddress(String ipAddress) {
int nMaskBits = 0;
if (ipAddress.indexOf('/') > 0) {
String[] addressAndMask = StringUtils.split(ipAddress, "/");
ipAddress = addressAndMask[0];
nMaskBits = Integer.parseInt(addressAndMask[1]);
}
InetAddress requiredAddress = parseAddress(ipAddress);
InetAddress remoteAddress = parseAddress(filterInvocation.getHttpRequest().getRemoteAddr());
if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
throw new IllegalArgumentException("IP Address in expression must be the same type as " +
"version returned by request");
}
if (nMaskBits == 0) {
return remoteAddress.equals(requiredAddress);
}
byte[] remAddr = remoteAddress.getAddress();
byte[] reqAddr = requiredAddress.getAddress();
int oddBits = nMaskBits % 8;
int nMaskBytes = nMaskBits/8 + (oddBits == 0 ? 0 : 1);
byte[] mask = new byte[nMaskBytes];
Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte)0xFF);
if (oddBits != 0) {
int finalByte = (1 << oddBits) - 1;
finalByte <<= 8-oddBits;
mask[mask.length - 1] = (byte) finalByte;
}
// System.out.println("Mask is " + new sun.misc.HexDumpEncoder().encode(mask));
for (int i=0; i < mask.length; i++) {
if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) {
return false;
}
}
return true;
}
private InetAddress parseAddress(String address) {
try {
return InetAddress.getByName(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Failed to parse address" + address, e);
}
}
}
@@ -1,60 +0,0 @@
package org.springframework.security.expression.web;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.intercept.web.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.intercept.web.RequestKey;
import org.springframework.security.util.UrlMatcher;
import org.springframework.util.Assert;
/**
* Expression-based <tt>FilterInvocationSecurityMetadataSource</tt>.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
public ExpressionBasedFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap, SecurityExpressionHandler expressionHandler) {
super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser()));
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
}
private static LinkedHashMap<RequestKey, List<ConfigAttribute>> processMap(
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap, ExpressionParser parser) {
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestToExpressionAttributesMap =
new LinkedHashMap<RequestKey, List<ConfigAttribute>>(requestMap);
for (Map.Entry<RequestKey, List<ConfigAttribute>> entry : requestMap.entrySet()) {
RequestKey request = entry.getKey();
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
String expression = entry.getValue().get(0).getAttribute();
logger.debug("Adding web access control expression '" + expression + "', for " + request);
try {
attributes.add(new WebExpressionConfigAttribute(parser.parseExpression(expression)));
} catch (ParseException e) {
throw new IllegalArgumentException("Failed to parse expression '" + expression + "'");
}
requestToExpressionAttributesMap.put(request, attributes);
}
return requestToExpressionAttributesMap;
}
}
@@ -1,32 +0,0 @@
package org.springframework.security.expression.web;
import org.springframework.expression.Expression;
import org.springframework.security.ConfigAttribute;
/**
* Simple expression configuration attribute for use in web request authorizations.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
class WebExpressionConfigAttribute implements ConfigAttribute {
private final Expression authorizeExpression;
public WebExpressionConfigAttribute(Expression authorizeExpression) {
this.authorizeExpression = authorizeExpression;
}
Expression getAuthorizeExpression() {
return authorizeExpression;
}
public String getAttribute() {
return null;
}
@Override
public String toString() {
return authorizeExpression.getExpressionString();
}
}
@@ -1,61 +0,0 @@
package org.springframework.security.expression.web;
import java.util.List;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.ExpressionUtils;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.expression.support.DefaultSecurityExpressionHandler;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.vote.AccessDecisionVoter;
/**
* Voter which handles web authorisation decisions.
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class WebExpressionVoter implements AccessDecisionVoter {
private SecurityExpressionHandler expressionHandler = new DefaultSecurityExpressionHandler();
public int vote(Authentication authentication, Object object, List<ConfigAttribute> attributes) {
assert authentication != null;
assert object != null;
assert attributes != null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
FilterInvocation fi = (FilterInvocation)object;
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ?
ACCESS_GRANTED : ACCESS_DENIED;
}
private WebExpressionConfigAttribute findConfigAttribute(List<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute)attribute;
}
}
return null;
}
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(FilterInvocation.class);
}
public void setExpressionHandler(SecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -1,224 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.intercept.web;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.util.UrlMatcher;
/**
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
* <p>
* Stores an ordered map of compiled URL paths to <tt>ConfigAttribute</tt> lists and provides URL matching
* against the items stored in this map using the configured <tt>UrlMatcher</tt>.
* <p>
* The order of registering the regular expressions using the
* {@link #addSecureUrl(String, List<ConfigAttribute>)} is very important.
* The system will identify the <b>first</b> matching regular
* expression for a given HTTP URL. It will not proceed to evaluate later regular expressions if a match has already
* been found. Accordingly, the most specific regular expressions should be registered first, with the most general
* regular expressions registered last.
* <p>
* If URLs are registered for a particular HTTP method using
* {@link #addSecureUrl(String, String, List<ConfigAttribute>)}, then the method-specific matches will take
* precedence over any URLs which are registered without an HTTP method.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
*/
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static final Set<String> HTTP_METHODS = new HashSet<String>(Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE"));
protected final Log logger = LogFactory.getLog(getClass());
//private Map<Object, List<ConfigAttribute>> requestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
/** Stores request maps keyed by specific HTTP methods. A null key matches any method */
private Map<String, Map<Object, List<ConfigAttribute>>> httpMethodMap =
new HashMap<String, Map<Object, List<ConfigAttribute>>>();
private UrlMatcher urlMatcher;
private boolean stripQueryStringFromUrls;
//~ Constructors ===================================================================================================
/**
* Builds the internal request map from the supplied map. The key elements should be of type {@link RequestKey},
* which contains a URL path and an optional HTTP method (may be null). The path stored in the key will depend on
* the type of the supplied UrlMatcher.
*
* @param urlMatcher typically an ant or regular expression matcher.
* @param requestMap order-preserving map of request definitions to attribute lists
*/
public DefaultFilterInvocationSecurityMetadataSource(UrlMatcher urlMatcher,
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap) {
this.urlMatcher = urlMatcher;
for (Map.Entry<RequestKey, List<ConfigAttribute>> entry : requestMap.entrySet()) {
addSecureUrl(entry.getKey().getUrl(), entry.getKey().getMethod(), entry.getValue());
}
}
//~ Methods ========================================================================================================
/**
* Adds a URL,attribute-list pair to the request map, first allowing the <tt>UrlMatcher</tt> to
* process the pattern if required, using its <tt>compile</tt> method. The returned object will be used as the key
* to the request map and will be passed back to the <tt>UrlMatcher</tt> when iterating through the map to find
* a match for a particular URL.
*/
private void addSecureUrl(String pattern, String method, List<ConfigAttribute> attr) {
Map<Object, List<ConfigAttribute>> mapToUse = getRequestMapForHttpMethod(method);
mapToUse.put(urlMatcher.compile(pattern), attr);
if (logger.isDebugEnabled()) {
logger.debug("Added URL pattern: " + pattern + "; attributes: " + attr +
(method == null ? "" : " for HTTP method '" + method + "'"));
}
}
/**
* Return the HTTP method specific request map, creating it if it doesn't already exist.
* @param method GET, POST etc
* @return map of URL patterns to <tt>ConfigAttribute</tt>s for this method.
*/
private Map<Object, List<ConfigAttribute>> getRequestMapForHttpMethod(String method) {
if (method != null && !HTTP_METHODS.contains(method)) {
throw new IllegalArgumentException("Unrecognised HTTP method: '" + method + "'");
}
Map<Object, List<ConfigAttribute>> methodRequestMap = httpMethodMap.get(method);
if (methodRequestMap == null) {
methodRequestMap = new LinkedHashMap<Object, List<ConfigAttribute>>();
httpMethodMap.put(method, methodRequestMap);
}
return methodRequestMap;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
for (Map.Entry<String, Map<Object, List<ConfigAttribute>>> entry : httpMethodMap.entrySet()) {
for (List<ConfigAttribute> attrs : entry.getValue().values()) {
allAttributes.addAll(attrs);
}
}
return allAttributes;
}
public List<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if ((object == null) || !this.supports(object.getClass())) {
throw new IllegalArgumentException("Object must be a FilterInvocation");
}
String url = ((FilterInvocation) object).getRequestUrl();
String method = ((FilterInvocation) object).getHttpRequest().getMethod();
return lookupAttributes(url, method);
}
/**
* Performs the actual lookup of the relevant <tt>ConfigAttribute</tt>s for the given <code>FilterInvocation</code>.
* <p>
* By default, iterates through the stored URL map and calls the
* {@link UrlMatcher#pathMatchesUrl(Object path, String url)} method until a match is found.
* <p>
* Subclasses can override if required to perform any modifications to the URL.
*
* @param url the URI to retrieve configuration attributes for
* @param method the HTTP method (GET, POST, DELETE...).
*
* @return the <code>ConfigAttribute</code>s that apply to the specified <code>FilterInvocation</code>
* or null if no match is found
*/
public final List<ConfigAttribute> lookupAttributes(String url, String method) {
if (stripQueryStringFromUrls) {
// Strip anything after a question mark symbol, as per SEC-161. See also SEC-321
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
}
if (urlMatcher.requiresLowerCaseUrl()) {
url = url.toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
}
}
// Obtain the map of request patterns to attributes for this method and lookup the url.
Map<Object, List<ConfigAttribute>> requestMap = httpMethodMap.get(method);
// If no method-specific map, use the general one stored under the null key
if (requestMap == null) {
requestMap = httpMethodMap.get(null);
}
if (requestMap != null) {
for (Map.Entry<Object, List<ConfigAttribute>> entry : requestMap.entrySet()) {
Object p = entry.getKey();
boolean matched = urlMatcher.pathMatchesUrl(entry.getKey(), url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + p + "; matched=" + matched);
}
if (matched) {
return entry.getValue();
}
}
}
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
protected UrlMatcher getUrlMatcher() {
return urlMatcher;
}
public boolean isConvertUrlToLowercaseBeforeComparison() {
return urlMatcher.requiresLowerCaseUrl();
}
public void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
this.stripQueryStringFromUrls = stripQueryStringFromUrls;
}
}
@@ -1,104 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.intercept.web;
import org.springframework.security.util.UrlUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Holds objects associated with a HTTP filter.<P>Guarantees the request and response are instances of
* <code>HttpServletRequest</code> and <code>HttpServletResponse</code>, and that there are no <code>null</code>
* objects.
* <p>
* Required so that security system classes can obtain access to the filter environment, as well as the request
* and response.
*
* @author Ben Alex
* @author colin sampaleanu
* @version $Id$
*/
public class FilterInvocation {
//~ Instance fields ================================================================================================
private FilterChain chain;
private HttpServletRequest request;
private HttpServletResponse response;
//~ Constructors ===================================================================================================
public FilterInvocation(ServletRequest request, ServletResponse response, FilterChain chain) {
if ((request == null) || (response == null) || (chain == null)) {
throw new IllegalArgumentException("Cannot pass null values to constructor");
}
this.request = (HttpServletRequest) request;
this.response = (HttpServletResponse) response;
this.chain = chain;
}
//~ Methods ========================================================================================================
public FilterChain getChain() {
return chain;
}
/**
* Indicates the URL that the user agent used for this request.
* <p>
* The returned URL does <b>not</b> reflect the port number determined from a
* {@link org.springframework.security.util.PortResolver}.
*
* @return the full URL of this request
*/
public String getFullRequestUrl() {
return UrlUtils.getFullRequestUrl(this);
}
public HttpServletRequest getHttpRequest() {
return request;
}
public HttpServletResponse getHttpResponse() {
return response;
}
/**
* Obtains the web application-specific fragment of the URL.
*
* @return the URL, excluding any server name, context path or servlet path
*/
public String getRequestUrl() {
return UrlUtils.getRequestUrl(this);
}
public HttpServletRequest getRequest() {
return getHttpRequest();
}
public HttpServletResponse getResponse() {
return getHttpResponse();
}
public String toString() {
return "FilterInvocation: URL: " + getRequestUrl();
}
}
@@ -1,28 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.intercept.web;
import org.springframework.security.intercept.SecurityMetadataSource;
/**
* Marker interface for <code>SecurityMetadataSource</code> implementations
* that are designed to perform lookups keyed on {@link FilterInvocation}s.
*
* @author Ben Alex
* @version $Id$
*/
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {}
@@ -1,155 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.intercept.web;
import org.springframework.security.intercept.AbstractSecurityInterceptor;
import org.springframework.security.intercept.InterceptorStatusToken;
import org.springframework.security.intercept.SecurityMetadataSource;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.core.Ordered;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* Performs security handling of HTTP resources via a filter implementation.
* <p>
* The <code>SecurityMetadataSource</code> required by this security interceptor is of type {@link
* FilterInvocationSecurityMetadataSource}.
* <p>
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter, Ordered {
//~ Static fields/initializers =====================================================================================
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
//~ Instance fields ================================================================================================
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
//~ Methods ========================================================================================================
/**
* Not used (we rely on IoC container lifecycle services instead)
*
* @param arg0 ignored
*
* @throws ServletException never thrown
*/
public void init(FilterConfig arg0) throws ServletException {}
/**
* Not used (we rely on IoC container lifecycle services instead)
*/
public void destroy() {}
/**
* Method that is actually called by the filter chain. Simply delegates to the {@link
* #invoke(FilterInvocation)} method.
*
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
*
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
/**
* Indicates whether once-per-request handling will be observed. By default this is <code>true</code>,
* meaning the <code>FilterSecurityInterceptor</code> will only execute once-per-request. Sometimes users may wish
* it to execute more than once per request, such as when JSP forwards are being used and filter security is
* desired on each included fragment of the HTTP request.
*
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise <code>false</code> if
* <code>FilterSecurityInterceptor</code> will enforce authorizations for each and every fragment of the
* HTTP request.
*/
public boolean isObserveOncePerRequest() {
return observeOncePerRequest;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
/**
* @deprecated use setSecurityMetadataSource instead
*/
public void setObjectDefinitionSource(FilterInvocationSecurityMetadataSource newSource) {
logger.warn("The property 'objectDefinitionSource' is deprecated. Please use 'securityMetadataSource' instead");
this.securityMetadataSource = newSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
public int getOrder() {
return FilterChainOrder.FILTER_SECURITY_INTERCEPTOR;
}
}
@@ -1,69 +0,0 @@
package org.springframework.security.intercept.web;
/**
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class RequestKey {
private String url;
private String method;
public RequestKey(String url) {
this(url, null);
}
public RequestKey(String url, String method) {
this.url = url;
this.method = method;
}
String getUrl() {
return url;
}
String getMethod() {
return method;
}
public int hashCode() {
int code = 31;
code ^= url.hashCode();
if (method != null) {
code ^= method.hashCode();
}
return code;
}
public boolean equals(Object obj) {
if (!(obj instanceof RequestKey)) {
return false;
}
RequestKey key = (RequestKey) obj;
if (!url.equals(key.url)) {
return false;
}
if (method == null) {
return key.method == null;
}
return method.equals(key.method);
}
public String toString() {
StringBuffer sb = new StringBuffer(url.length() + 7);
sb.append("[");
if (method != null) {
sb.append(method).append(",");
}
sb.append(url);
sb.append("]");
return sb.toString();
}
}
@@ -1,90 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.intercept.web;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.AbstractSecurityInterceptor;
import org.springframework.util.Assert;
/**
* Allows users to determine whether they have privileges for a given web URI.
*
* @author Ben Alex
* @version $Id$
*/
public class WebInvocationPrivilegeEvaluator implements InitializingBean {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(WebInvocationPrivilegeEvaluator.class);
//~ Instance fields ================================================================================================
private AbstractSecurityInterceptor securityInterceptor;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(securityInterceptor, "SecurityInterceptor required");
}
public boolean isAllowed(FilterInvocation fi, Authentication authentication) {
Assert.notNull(fi, "FilterInvocation required");
List<ConfigAttribute> attrs = securityInterceptor.obtainSecurityMetadataSource().getAttributes(fi);
if (attrs == null) {
if (securityInterceptor.isRejectPublicInvocations()) {
return false;
}
return true;
}
if ((authentication == null) || (authentication.getAuthorities() == null)
|| authentication.getAuthorities().isEmpty()) {
return false;
}
try {
securityInterceptor.getAccessDecisionManager().decide(authentication, fi, attrs);
} catch (AccessDeniedException unauthorized) {
if (logger.isDebugEnabled()) {
logger.debug(fi.toString() + " denied for " + authentication.toString(), unauthorized);
}
return false;
}
return true;
}
public void setSecurityInterceptor(AbstractSecurityInterceptor securityInterceptor) {
Assert.notNull(securityInterceptor, "AbstractSecurityInterceptor cannot be null");
Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()),
"AbstractSecurityInterceptor does not support FilterInvocations");
Assert.notNull(securityInterceptor.getAccessDecisionManager(),
"AbstractSecurityInterceptor must provide a non-null AccessDecisionManager");
this.securityInterceptor = securityInterceptor;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Enforces security for HTTP requests, typically by the URL requested.
</body>
</html>
@@ -1,156 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.providers.anonymous;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.WebAuthenticationDetailsSource;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.userdetails.memory.UserAttribute;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Detects if there is no <code>Authentication</code> object in the <code>SecurityContextHolder</code>, and
* populates it with one if needed.
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousProcessingFilter extends SpringSecurityFilter implements InitializingBean {
//~ Instance fields ================================================================================================
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private String key;
private UserAttribute userAttribute;
private boolean removeAfterRequest = true;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(userAttribute);
Assert.hasLength(key);
}
/**
* Enables subclasses to determine whether or not an anonymous authentication token should be setup for
* this request. This is useful if anonymous authentication should be allowed only for specific IP subnet ranges
* etc.
*
* @param request to assist the method determine request details
*
* @return <code>true</code> if the anonymous token should be setup for this request (provided that the request
* doesn't already have some other <code>Authentication</code> inside it), or <code>false</code> if no
* anonymous token should be setup for this request
*/
protected boolean applyAnonymousForThisRequest(HttpServletRequest request) {
return true;
}
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key, userAttribute.getPassword(),
userAttribute.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
return auth;
}
protected void doFilterHttp(HttpServletRequest request,HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean addedToken = false;
if (applyAnonymousForThisRequest(request)) {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(createAuthentication(request));
addedToken = true;
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
}
try {
chain.doFilter(request, response);
} finally {
if (addedToken && removeAfterRequest
&& createAuthentication(request).equals(SecurityContextHolder.getContext().getAuthentication())) {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
}
public int getOrder() {
return FilterChainOrder.ANONYMOUS_FILTER;
}
public String getKey() {
return key;
}
public UserAttribute getUserAttribute() {
return userAttribute;
}
public boolean isRemoveAfterRequest() {
return removeAfterRequest;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setKey(String key) {
this.key = key;
}
/**
* Controls whether the filter will remove the Anonymous token after the request is complete. Generally
* this is desired to avoid the expense of a session being created by {@link
* org.springframework.security.context.HttpSessionContextIntegrationFilter HttpSessionContextIntegrationFilter} simply to
* store the Anonymous authentication token.<p>Defaults to <code>true</code>, being the most optimal and
* appropriate option (ie <code>AnonymousProcessingFilter</code> will clear the token at the end of each request,
* thus avoiding the session creation overhead in a typical configuration.</p>
*
* @param removeAfterRequest DOCUMENT ME!
*/
public void setRemoveAfterRequest(boolean removeAfterRequest) {
this.removeAfterRequest = removeAfterRequest;
}
public void setUserAttribute(UserAttribute userAttributeDefinition) {
this.userAttribute = userAttributeDefinition;
}
}
@@ -41,13 +41,12 @@ import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.SpringSecurityException;
import org.springframework.security.context.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.event.SessionDestroyedEvent;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.jaas.event.JaasAuthenticationFailedEvent;
import org.springframework.security.providers.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.ui.session.HttpSessionDestroyedEvent;
import org.springframework.util.Assert;
@@ -311,9 +310,8 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
*
* @param event
*/
protected void handleLogout(HttpSessionDestroyedEvent event) {
SecurityContext context = (SecurityContext)
event.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
protected void handleLogout(SessionDestroyedEvent event) {
SecurityContext context = event.getSecurityContext();
if (context == null) {
log.debug("The destroyed session has no SecurityContext");
@@ -343,8 +341,8 @@ public class JaasAuthenticationProvider implements AuthenticationProvider, Appli
}
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof HttpSessionDestroyedEvent) {
HttpSessionDestroyedEvent event = (HttpSessionDestroyedEvent) applicationEvent;
if (applicationEvent instanceof SessionDestroyedEvent) {
SessionDestroyedEvent event = (SessionDestroyedEvent) applicationEvent;
handleLogout(event);
}
}
@@ -22,7 +22,7 @@ import org.springframework.util.Assert;
*
* <p>
* This authentication provider will not perform any checks on authentication
* requests, as they should already be pre- authenticated. However, the
* requests, as they should already be pre-authenticated. However, the
* AuthenticationUserDetailsService implementation may still throw a UsernameNotFoundException, for example.
*
* @author Ruud Senden
@@ -13,7 +13,7 @@
* limitations under the License.
*/
package org.springframework.security.context.httpinvoker;
package org.springframework.security.remoting.httpinvoker;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationCredentialsNotFoundException;
@@ -13,7 +13,7 @@
* limitations under the License.
*/
package org.springframework.security.context.rmi;
package org.springframework.security.remoting.rmi;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
@@ -13,7 +13,7 @@
* limitations under the License.
*/
package org.springframework.security.context.rmi;
package org.springframework.security.remoting.rmi;
import org.aopalliance.intercept.MethodInvocation;
@@ -1,90 +0,0 @@
package org.springframework.security.securechannel;
import org.springframework.security.util.PortMapper;
import org.springframework.security.util.PortResolver;
import org.springframework.security.util.PortMapperImpl;
import org.springframework.security.util.PortResolverImpl;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint {
//~ Static fields/initializers =====================================================================================
protected final Log logger = LogFactory.getLog(getClass());
//~ Instance fields ================================================================================================
private PortMapper portMapper = new PortMapperImpl();
private PortResolver portResolver = new PortResolverImpl();
/** The scheme ("http://" or "https://") */
private String scheme;
/** The standard port for the scheme (80 for http, 443 for https) */
private int standardPort;
//~ Constructors ===================================================================================================
public AbstractRetryEntryPoint(String scheme, int standardPort) {
this.scheme = scheme;
this.standardPort = standardPort;
}
//~ Methods ========================================================================================================
public void commence(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String pathInfo = request.getPathInfo();
String queryString = request.getQueryString();
String contextPath = request.getContextPath();
String destination = request.getServletPath() + ((pathInfo == null) ? "" : pathInfo)
+ ((queryString == null) ? "" : ("?" + queryString));
String redirectUrl = contextPath;
Integer currentPort = new Integer(portResolver.getServerPort(request));
Integer redirectPort = getMappedPort(currentPort);
if (redirectPort != null) {
boolean includePort = redirectPort.intValue() != standardPort;
redirectUrl = scheme + request.getServerName() + ((includePort) ? (":" + redirectPort) : "") + contextPath
+ destination;
}
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to: " + redirectUrl);
}
((HttpServletResponse) res).sendRedirect(((HttpServletResponse) res).encodeRedirectURL(redirectUrl));
}
protected abstract Integer getMappedPort(Integer mapFromPort);
protected PortMapper getPortMapper() {
return portMapper;
}
protected PortResolver getPortResolver() {
return portResolver;
}
public void setPortMapper(PortMapper portMapper) {
Assert.notNull(portMapper, "portMapper cannot be null");
this.portMapper = portMapper;
}
public void setPortResolver(PortResolver portResolver) {
Assert.notNull(portResolver, "portResolver cannot be null");
this.portResolver = portResolver;
}
}
@@ -1,54 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
/**
* Decides whether a web channel provides sufficient security.
*
* @author Ben Alex
* @version $Id$
*/
public interface ChannelDecisionManager {
//~ Methods ========================================================================================================
/**
* Decided whether the presented {@link FilterInvocation} provides the appropriate level of channel
* security based on the requested list of <tt>ConfigAttribute</tt>s.
*
*/
void decide(FilterInvocation invocation, List<ConfigAttribute> config) throws IOException, ServletException;
/**
* Indicates whether this <code>ChannelDecisionManager</code> is able to process the passed
* <code>ConfigAttribute</code>.<p>This allows the <code>ChannelProcessingFilter</code> to check every
* configuration attribute can be consumed by the configured <code>ChannelDecisionManager</code>.</p>
*
* @param attribute a configuration attribute that has been configured against the
* <code>ChannelProcessingFilter</code>
*
* @return true if this <code>ChannelDecisionManager</code> can support the passed configuration attribute
*/
boolean supports(ConfigAttribute attribute);
}
@@ -1,113 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
/**
* Implementation of {@link ChannelDecisionManager}.
* <p>
* Iterates through each configured {@link ChannelProcessor}. If a <code>ChannelProcessor</code> has any issue with the
* security of the request, it should cause a redirect, exception or whatever other action is appropriate for the
* <code>ChannelProcessor</code> implementation.
* <p>
* Once any response is committed (ie a redirect is written to the response object), the
* <code>ChannelDecisionManagerImpl</code> will not iterate through any further <code>ChannelProcessor</code>s.
* <p>
* The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the channel processors will be
* skipped (see SEC-494, SEC-335).
*
* @author Ben Alex
* @version $Id$
*/
public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean {
public static final String ANY_CHANNEL = "ANY_CHANNEL";
//~ Instance fields ================================================================================================
private List<ChannelProcessor> channelProcessors;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notEmpty(channelProcessors, "A list of ChannelProcessors is required");
}
public void decide(FilterInvocation invocation, List<ConfigAttribute> config) throws IOException, ServletException {
Iterator<ConfigAttribute> attrs = config.iterator();
while (attrs.hasNext()) {
ConfigAttribute attribute = attrs.next();
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
return;
}
}
for (ChannelProcessor processor : channelProcessors) {
processor.decide(invocation, config);
if (invocation.getResponse().isCommitted()) {
break;
}
}
}
protected List<ChannelProcessor> getChannelProcessors() {
return this.channelProcessors;
}
@SuppressWarnings("cast")
public void setChannelProcessors(List<?> newList) {
Assert.notEmpty(newList, "A list of ChannelProcessors is required");
channelProcessors = new ArrayList<ChannelProcessor>(newList.size());
for (Object currentObject : newList) {
Assert.isInstanceOf(ChannelProcessor.class, currentObject, "ChannelProcessor " +
currentObject.getClass().getName() + " must implement ChannelProcessor");
channelProcessors.add((ChannelProcessor)currentObject);
}
}
public boolean supports(ConfigAttribute attribute) {
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
return true;
}
for (ChannelProcessor processor : channelProcessors) {
if (processor.supports(attribute)) {
return true;
}
}
return false;
}
}
@@ -1,50 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* May be used by a {@link ChannelProcessor} to launch a web channel.
*
* <p>
* <code>ChannelProcessor</code>s can elect to launch a new web channel directly, or they can delegate to another class.
* The <code>ChannelEntryPoint</code> is a pluggable interface to assist <code>ChannelProcessor</code>s in performing
* this delegation.
*
* @author Ben Alex
* @version $Id$
*/
public interface ChannelEntryPoint {
//~ Methods ========================================================================================================
/**
* Commences a secure channel.
* <p>
* Implementations should modify the headers on the <code>ServletResponse</code> as necessary to commence the user
* agent using the implementation's supported channel type.
*
* @param request that a <code>ChannelProcessor</code> has rejected
* @param response so that the user agent can begin using a new channel
*
*/
void commence(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}
@@ -1,130 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.intercept.web.FilterInvocationSecurityMetadataSource;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.util.Assert;
/**
* Ensures a web request is delivered over the required channel.
* <p>Internally uses a {@link FilterInvocation} to represent the request, so that the
* <code>FilterInvocation</code>-related property editors and lookup classes can be used.</p>
* <p>Delegates the actual channel security decisions and necessary actions to the configured
* {@link ChannelDecisionManager}. If a response is committed by the <code>ChannelDecisionManager</code>,
* the filter chain will not proceed.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class ChannelProcessingFilter extends SpringSecurityFilter implements InitializingBean {
//~ Instance fields ================================================================================================
private ChannelDecisionManager channelDecisionManager;
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(filterInvocationSecurityMetadataSource, "filterInvocationSecurityMetadataSource must be specified");
Assert.notNull(channelDecisionManager, "channelDecisionManager must be specified");
Collection<ConfigAttribute> attrDefs = this.filterInvocationSecurityMetadataSource.getAllConfigAttributes();
if (attrDefs == null) {
if (logger.isWarnEnabled()) {
logger.warn("Could not validate configuration attributes as the FilterInvocationSecurityMetadataSource did "
+ "not return any attributes");
}
return;
}
Set<ConfigAttribute> unsupportedAttributes = new HashSet<ConfigAttribute>();
for (ConfigAttribute attr : attrDefs) {
if (!this.channelDecisionManager.supports(attr)) {
unsupportedAttributes.add(attr);
}
}
if (unsupportedAttributes.size() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Validated configuration attributes");
}
} else {
throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttributes);
}
}
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
List<ConfigAttribute> attr = this.filterInvocationSecurityMetadataSource.getAttributes(fi);
if (attr != null) {
if (logger.isDebugEnabled()) {
logger.debug("Request: " + fi.toString() + "; ConfigAttributes: " + attr);
}
channelDecisionManager.decide(fi, attr);
if (fi.getResponse().isCommitted()) {
return;
}
}
chain.doFilter(request, response);
}
public ChannelDecisionManager getChannelDecisionManager() {
return channelDecisionManager;
}
public FilterInvocationSecurityMetadataSource getFilterInvocationSecurityMetadataSource() {
return filterInvocationSecurityMetadataSource;
}
public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) {
this.channelDecisionManager = channelDecisionManager;
}
public void setFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) {
this.filterInvocationSecurityMetadataSource = filterInvocationSecurityMetadataSource;
}
public int getOrder() {
return FilterChainOrder.CHANNEL_FILTER;
}
}
@@ -1,61 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
/**
* Decides whether a web channel meets a specific security condition.
* <p>
* <code>ChannelProcessor</code> implementations are iterated by the {@link ChannelDecisionManagerImpl}.
* <p>
* If an implementation has an issue with the channel security, they should
* take action themselves. The callers of the implementation do not take any
* action.
*
* @author Ben Alex
* @version $Id$
*/
public interface ChannelProcessor {
//~ Methods ========================================================================================================
/**
* Decided whether the presented {@link FilterInvocation} provides the appropriate level of channel
* security based on the requested list of <tt>ConfigAttribute</tt>s.
*
*/
void decide(FilterInvocation invocation, List<ConfigAttribute> config) throws IOException, ServletException;
/**
* Indicates whether this <code>ChannelProcessor</code> is able to process the passed
* <code>ConfigAttribute</code>.
* <p>
* This allows the <code>ChannelProcessingFilter</code> to check every configuration attribute can be consumed
* by the configured <code>ChannelDecisionManager</code>.
*
* @param attribute a configuration attribute that has been configured against the <tt>ChannelProcessingFilter</tt>.
*
* @return true if this <code>ChannelProcessor</code> can support the passed configuration attribute
*/
boolean supports(ConfigAttribute attribute);
}
@@ -1,93 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.util.Assert;
/**
* Ensures channel security is inactive by review of <code>HttpServletRequest.isSecure()</code> responses.
* <p>
* The class responds to one case-sensitive keyword, {@link #getInsecureKeyword}. If this keyword is detected,
* <code>HttpServletRequest.isSecure()</code> is used to determine the channel security offered. If channel security
* is present, the configured <code>ChannelEntryPoint</code> is called. By default the entry point is {@link
* RetryWithHttpEntryPoint}.
* <p>
* The default <code>insecureKeyword</code> is <code>REQUIRES_INSECURE_CHANNEL</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class InsecureChannelProcessor implements InitializingBean, ChannelProcessor {
//~ Instance fields ================================================================================================
private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint();
private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(insecureKeyword, "insecureKeyword required");
Assert.notNull(entryPoint, "entryPoint required");
}
public void decide(FilterInvocation invocation, List<ConfigAttribute> config) throws IOException, ServletException {
if ((invocation == null) || (config == null)) {
throw new IllegalArgumentException("Nulls cannot be provided");
}
for (ConfigAttribute attribute : config) {
if (supports(attribute)) {
if (invocation.getHttpRequest().isSecure()) {
entryPoint.commence(invocation.getRequest(), invocation.getResponse());
}
}
}
}
public ChannelEntryPoint getEntryPoint() {
return entryPoint;
}
public String getInsecureKeyword() {
return insecureKeyword;
}
public void setEntryPoint(ChannelEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public void setInsecureKeyword(String secureKeyword) {
this.insecureKeyword = secureKeyword;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute != null) && (attribute.getAttribute() != null)
&& attribute.getAttribute().equals(getInsecureKeyword())) {
return true;
} else {
return false;
}
}
}
@@ -1,37 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
/**
* Commences an insecure channel by retrying the original request using HTTP.
* <p>
* This entry point should suffice in most circumstances. However, it is not intended to properly handle HTTP POSTs or
* other usage where a standard redirect would cause an issue.
*
* @author Ben Alex
* @version $Id$
*/
public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint {
public RetryWithHttpEntryPoint() {
super("http://", 80);
}
protected Integer getMappedPort(Integer mapFromPort) {
return getPortMapper().lookupHttpPort(mapFromPort);
}
}
@@ -1,36 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
/**
* Commences a secure channel by retrying the original request using HTTPS.
* <p>
* This entry point should suffice in most circumstances. However, it is not intended to properly handle HTTP POSTs
* or other usage where a standard redirect would cause an issue.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint {
public RetryWithHttpsEntryPoint() {
super("https://", 443);
}
protected Integer getMappedPort(Integer mapFromPort) {
return getPortMapper().lookupHttpsPort(mapFromPort);
}
}
@@ -1,91 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.securechannel;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.util.Assert;
/**
* Ensures channel security is active by review of <code>HttpServletRequest.isSecure()</code> responses.
* <p>
* The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. If this keyword is detected,
* <code>HttpServletRequest.isSecure()</code> is used to determine the channel security offered. If channel security
* is not present, the configured <code>ChannelEntryPoint</code> is called. By default the entry point is {@link
* RetryWithHttpsEntryPoint}.
* <p>
* The default <code>secureKeyword</code> is <code>REQUIRES_SECURE_CHANNEL</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class SecureChannelProcessor implements InitializingBean, ChannelProcessor {
//~ Instance fields ================================================================================================
private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint();
private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(secureKeyword, "secureKeyword required");
Assert.notNull(entryPoint, "entryPoint required");
}
public void decide(FilterInvocation invocation, List<ConfigAttribute> config) throws IOException, ServletException {
Assert.isTrue((invocation != null) && (config != null), "Nulls cannot be provided");
for (ConfigAttribute attribute : config) {
if (supports(attribute)) {
if (!invocation.getHttpRequest().isSecure()) {
entryPoint.commence(invocation.getRequest(), invocation.getResponse());
}
}
}
}
public ChannelEntryPoint getEntryPoint() {
return entryPoint;
}
public String getSecureKeyword() {
return secureKeyword;
}
public void setEntryPoint(ChannelEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public void setSecureKeyword(String secureKeyword) {
this.secureKeyword = secureKeyword;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute != null) && (attribute.getAttribute() != null)
&& attribute.getAttribute().equals(getSecureKeyword())) {
return true;
} else {
return false;
}
}
}
@@ -1,6 +0,0 @@
<html>
<body>
Classes that ensure web requests are received over required
transport channels.
</body>
</html>
@@ -1,169 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.Authentication;
import org.springframework.security.ui.logout.LogoutHandler;
import org.springframework.security.util.RedirectUtils;
import org.springframework.security.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class containing the logic used by strategies which handle redirection to a URL and
* are passed an <tt>Authentication</tt> object as part of the contract.
* See {@link AuthenticationSuccessHandler} and {@link LogoutHandler}, for example.
* <p>
* Uses the following logic sequence to determine how it should handle the forward/redirect
* <ul>
* <li>
* If the <tt>alwaysUseDefaultTargetUrl</tt> property is set to true, the <tt>defaultTargetUrl</tt> property
* will be used for the destination.
* </li>
* <li>
* If a parameter matching the <tt>targetUrlParameter</tt> has been set on the request, the value will be used as
* the destination.
* </li>
* <li>
* If the <tt>useReferer</tt> property is set, the "Referer" HTTP header value will be used, if present.
* </li>
* <li>
* As a fallback option, the <tt>defaultTargetUrl</tt> value will be used.
* </li>
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public abstract class AbstractAuthenticationTargetUrlRequestHandler {
public static String DEFAULT_TARGET_PARAMETER = "spring-security-redirect";
protected final Log logger = LogFactory.getLog(this.getClass());
private String targetUrlParameter = DEFAULT_TARGET_PARAMETER;
private String defaultTargetUrl = "/";
private boolean alwaysUseDefaultTargetUrl = false;
private boolean useRelativeContext = false;
private boolean useReferer = false;
protected AbstractAuthenticationTargetUrlRequestHandler() {
}
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response);
RedirectUtils.sendRedirect(request, response, targetUrl, useRelativeContext);
}
private String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
if (isAlwaysUseDefaultTargetUrl()) {
return defaultTargetUrl;
}
// Check for the parameter and use that if available
String targetUrl = request.getParameter(targetUrlParameter);
if (StringUtils.hasText(targetUrl)) {
try {
targetUrl = URLDecoder.decode(targetUrl, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 not supported. Shouldn't be possible");
}
logger.debug("Found targetUrlParameter in request: " + targetUrl);
return targetUrl;
}
if (useReferer && !StringUtils.hasLength(targetUrl)) {
targetUrl = request.getHeader("Referer");
logger.debug("Using Referer header: " + targetUrl);
}
if (!StringUtils.hasText(targetUrl)) {
targetUrl = defaultTargetUrl;
logger.debug("Using default Url: " + targetUrl);
}
return targetUrl;
}
/**
* Supplies the default target Url that will be used if no saved request is found or the
* <tt>alwaysUseDefaultTargetUrl</tt> property is set to true. If not set, defaults to <tt>/</tt>.
*
* @return the defaultTargetUrl property
*/
protected String getDefaultTargetUrl() {
return defaultTargetUrl;
}
/**
* Supplies the default target Url that will be used if no saved request is found in the session, or the
* <tt>alwaysUseDefaultTargetUrl</tt> property is set to true. If not set, defaults to <tt>/</tt>. It
* will be treated as relative to the web-app's context path, and should include the leading <code>/</code>.
* Alternatively, inclusion of a scheme name (such as "http://" or "https://") as the prefix will denote a
* fully-qualified URL and this is also supported.
*
* @param defaultTargetUrl
*/
public void setDefaultTargetUrl(String defaultTargetUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultTargetUrl),
"defaultTarget must start with '/' or with 'http(s)'");
this.defaultTargetUrl = defaultTargetUrl;
}
/**
* If <code>true</code>, will always redirect to the value of <tt>defaultTargetUrl</tt>
* (defaults to <code>false</code>).
*/
public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
}
protected boolean isAlwaysUseDefaultTargetUrl() {
return alwaysUseDefaultTargetUrl;
}
/**
* The current request will be checked for this parameter before and the value used as the target URL if resent.
*
* @param targetUrlParameter the name of the parameter containing the encoded target URL. Defaults
* to "redirect".
*/
public void setTargetUrlParameter(String targetUrlParameter) {
Assert.hasText("targetUrlParameter canot be null or empty");
this.targetUrlParameter = targetUrlParameter;
}
protected String getTargetUrlParameter() {
return targetUrlParameter;
}
/**
* If <tt>true</tt>, causes any redirection URLs to be calculated minus the protocol
* and context path (defaults to <tt>false</tt>).
*/
public void setUseRelativeContext(boolean useRelativeContext) {
this.useRelativeContext = useRelativeContext;
}
protected boolean isUseRelativeContext() {
return useRelativeContext;
}
/**
* If set to <tt>true</tt> the <tt>Referer</tt> header will be used (if available). Defaults to <tt>false</tt>.
*/
public void setUseReferer(boolean useReferer) {
this.useReferer = useReferer;
}
}
@@ -1,439 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.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.HttpSession;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.concurrent.SessionRegistry;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.event.authentication.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.ui.rememberme.NullRememberMeServices;
import org.springframework.security.ui.rememberme.RememberMeServices;
import org.springframework.security.util.SessionUtils;
import org.springframework.security.util.UrlUtils;
import org.springframework.util.Assert;
/**
* Abstract processor of browser-based HTTP-based authentication requests.
*
* <h3>Authentication Process</h3>
*
* The filter requires that you set the <tt>authenticationManager</tt> property. An <tt>AuthenticationManager</tt> is
* required to process the authentication request tokens created by implementing classes.
* <p>
* This filter will intercept a request and attempt to perform authentication from that request if
* the request URL matches the value of the <tt>filterProcessesUrl</tt> property. This behaviour can modified by
* overriding the method {@link #requiresAuthentication(HttpServletRequest, HttpServletResponse) requiresAuthentication}.
* <p>
* Authentication is performed by the {@link #attemptAuthentication(HttpServletRequest, HttpServletResponse)
* attemptAuthentication} method, which must be implemented by subclasses.
*
* <h4>Authentication Success</h4>
*
* If authentication is successful, the resulting {@link Authentication} object will be placed into the
* <code>SecurityContext</code> for the current thread, which is guaranteed to have already been created by an earlier
* filter. The configured {@link #setAuthenticationSuccessHandler(AuthenticationSuccessHandler) AuthenticationSuccessHandler} will
* then be called to take the redirect to the appropriate destination after a successful login. The default behaviour
* is implemented in a {@link SavedRequestAwareAuthenticationSuccessHandler} which will make use of any
* <tt>SavedRequest</tt> set by the <tt>ExceptionTranslationFilter</tt> and redirect the user to the URL contained
* therein. Otherwise it will redirect to the webapp root "/". You can customize this behaviour by injecting a
* differently configured instance of this class, or by using a different implementation.
* <p>
* See the {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, Authentication)
* successfulAuthentication} method for more information.
*
* <h4>Authentication Failure</h4>
*
* If authentication fails, the resulting <tt>AuthenticationException</tt> will be placed into the <tt>HttpSession</tt>
* with the attribute defined by {@link #SPRING_SECURITY_LAST_EXCEPTION_KEY}. It will then delegate to the configured
* {@link AuthenticationFailureHandler} to allow the failure information to be conveyed to the client.
* The default implementation is {@link SimpleUrlAuthenticationFailureHandler}, which sends a 401 error code to the
* client. It may also be configured with a failure URL as an alternative. Again you can inject whatever
* behaviour you require here.
*
* <h4>Event Publication</h4>
*
* If authentication is successful, an
* {@link org.springframework.security.event.authentication.InteractiveAuthenticationSuccessEvent
* InteractiveAuthenticationSuccessEvent} will be published via the application context. No events will be published if
* authentication was unsuccessful, because this would generally be recorded via an
* <tt>AuthenticationManager</tt>-specific application event.
* <p>
* The filter has an optional attribute <tt>invalidateSessionOnSuccessfulAuthentication</tt> that will invalidate
* the current session on successful authentication. This is to protect against session fixation attacks (see
* <a href="http://en.wikipedia.org/wiki/Session_fixation">this Wikipedia article</a> for more information).
* The behaviour is turned off by default. Additionally there is a property <tt>migrateInvalidatedSessionAttributes</tt>
* which tells if on session invalidation we are to migrate all session attributes from the old session to a newly
* created one. This is turned on by default, but not used unless <tt>invalidateSessionOnSuccessfulAuthentication</tt>
* is true. If you are using this feature in combination with concurrent session control, you should set the
* <tt>sessionRegistry</tt> property to make sure that the session information is updated consistently.
*
* @author Ben Alex
* @version $Id$
*/
public abstract class AbstractProcessingFilter extends SpringSecurityFilter implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
public static final String SPRING_SECURITY_LAST_EXCEPTION_KEY = "SPRING_SECURITY_LAST_EXCEPTION";
//~ Instance fields ================================================================================================
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/*
* Delay use of NullRememberMeServices until initialization so that namespace has a chance to inject
* the RememberMeServices implementation into custom implementations.
*/
private RememberMeServices rememberMeServices = null;
/**
* The URL destination that this filter intercepts and processes (usually
* something like <code>/j_spring_security_check</code>)
*/
private String filterProcessesUrl;
private boolean continueChainBeforeSuccessfulAuthentication = false;
/**
* Tells if we on successful authentication should invalidate the
* current session. This is a common guard against session fixation attacks.
* Defaults to <code>false</code>.
*/
private boolean invalidateSessionOnSuccessfulAuthentication = false;
/**
* If {@link #invalidateSessionOnSuccessfulAuthentication} is true, this
* flag indicates that the session attributes of the session to be invalidated
* are to be migrated to the new session. Defaults to <code>true</code> since
* nothing will happen unless {@link #invalidateSessionOnSuccessfulAuthentication}
* is true.
*/
private boolean migrateInvalidatedSessionAttributes = true;
private boolean allowSessionCreation = true;
private SessionRegistry sessionRegistry;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
//~ Constructors ===================================================================================================
/**
* @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>.
*/
protected AbstractProcessingFilter(String defaultFilterProcessesUrl) {
this.filterProcessesUrl = defaultFilterProcessesUrl;
}
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
Assert.notNull(authenticationManager, "authenticationManager must be specified");
if (rememberMeServices == null) {
rememberMeServices = new NullRememberMeServices();
}
}
/**
* Invokes the {@link #requiresAuthentication(HttpServletRequest, HttpServletResponse) requiresAuthentication}
* method to determine whether the request is for authentication and should be handled by this filter.
* If it is an authentication request, the
* {@link #attemptAuthentication(HttpServletRequest, HttpServletResponse) attemptAuthentication} will be invoked
* to perform the authentication. There are then three possible outcomes:
* <ol>
* <li>An <tt>Authentication</tt> object is returned.
* The {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, Authentication)
* successfulAuthentication} method will be invoked</li>
* <li>An <tt>AuthenticationException</tt> occurs during authentication.
* The {@link #unSuccessfulAuthentication(HttpServletRequest, HttpServletResponse, Authentication)
* unSuccessfulAuthentication} method will be invoked</li>
* <li>Null is returned, indicating that the authentication process is incomplete.
* The method will then return immediately, assuming that the subclass has done any necessary work (such as
* redirects) to continue the authentication process. The assumption is that a later request will be received
* by this method where the returned <tt>Authentication</tt> object is not null.
* </ol>
*/
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed authentication
return;
}
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, authResult);
}
/**
* Indicates whether this filter should attempt to process a login request for the current invocation.
* <p>
* It strips any parameters from the "path" section of the request URL (such
* as the jsessionid parameter in
* <em>http://host/myapp/index.html;jsessionid=blah</em>) before matching
* against the <code>filterProcessesUrl</code> property.
* <p>
* Subclasses may override for special requirements, such as Tapestry integration.
*
* @return <code>true</code> if the filter should attempt authentication, <code>false</code> otherwise.
*/
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) {
// strip everything after the first semi-colon
uri = uri.substring(0, pathParamIndex);
}
if ("".equals(request.getContextPath())) {
return uri.endsWith(filterProcessesUrl);
}
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
/**
* Performs actual authentication.
* <p>
* The implementation should do one of the following:
* <ol>
* <li>Return a populated authentication token for the authenticated user, indicating successful authentication</li>
* <li>Return null, indicating that the authentication process is still in progress. Before returning, the
* implementation should perform any additional work required to complete the process.</li>
* <li>Throw an <tt>AuthenticationException</tt> if the authentication process fails</li>
* </ol>
*
* @param request from which to extract parameters and perform the authentication
* @param response the response, which may be needed if the implementation has to do a redirect as part of a
* multi-stage authentication process (such as OpenID).
*
* @return the authenticated user token, or null if authentication is incomplete.
*
* @throws AuthenticationException if authentication fails.
*/
public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException;
/**
* Default behaviour for successful authentication.
* <ol>
* <li>Sets the successful <tt>Authentication</tt> object on the {@link SecurityContextHolder}</li>
* <li>Performs any configured session migration behaviour</li>
* <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>
* <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
* <tt>ApplicationEventPublisher</tt></li>
* <li>Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.</li>
* </ol>
*
* @param authResult the object returned from the <tt>attemptAuthentication</tt> method.
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
if (invalidateSessionOnSuccessfulAuthentication) {
SessionUtils.startNewSessionIfRequired(request, migrateInvalidatedSessionAttributes, sessionRegistry);
}
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
/**
* Default behaviour for unsuccessful authentication.
* <ol>
* <li>Clears the {@link SecurityContextHolder}</li>
* <li>Stores the exception in the session (if it exists or <tt>allowSesssionCreation</tt> is set to <tt>true</tt>)</li>
* <li>Informs the configured <tt>RememberMeServices</tt> of the failed login</li>
* <li>Delegates additional behaviour to the {@link AuthenticationFailureHandler}.</li>
* </ol>
*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString());
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler" + failureHandler);
}
try {
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}
}
catch (Exception ignored) {
}
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public String getFilterProcessesUrl() {
return filterProcessesUrl;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.filterProcessesUrl = filterProcessesUrl;
}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
this.rememberMeServices = rememberMeServices;
}
/**
* Indicates if the filter chain should be continued prior to delegation to
* {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse,
* Authentication)}, which may be useful in certain environment (such as
* Tapestry applications). Defaults to <code>false</code>.
*/
public void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setInvalidateSessionOnSuccessfulAuthentication(boolean invalidateSessionOnSuccessfulAuthentication) {
this.invalidateSessionOnSuccessfulAuthentication = invalidateSessionOnSuccessfulAuthentication;
}
public void setMigrateInvalidatedSessionAttributes(boolean migrateInvalidatedSessionAttributes) {
this.migrateInvalidatedSessionAttributes = migrateInvalidatedSessionAttributes;
}
public AuthenticationDetailsSource getAuthenticationDetailsSource() {
// Required due to SEC-310
return authenticationDetailsSource;
}
protected boolean getAllowSessionCreation() {
return allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
/**
* The session registry needs to be set if session fixation attack protection is in use (and concurrent
* session control is enabled).
*/
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
/**
* Sets the strategy used to handle a successful authentication.
* By default a {@link SavedRequestAwareAuthenticationSuccessHandler} is used.
*/
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
}
@@ -1,49 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import org.springframework.security.AccessDeniedException;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Used by {@link ExceptionTranslationFilter} to handle an
* <code>AccessDeniedException</code>.
*
* @author Ben Alex
* @version $Id$
*/
public interface AccessDeniedHandler {
//~ Methods ========================================================================================================
/**
* Handles an access denied failure.
*
* @param request that resulted in an <code>AccessDeniedException</code>
* @param response so that the user agent can be advised of the failure
* @param accessDeniedException that caused the invocation
*
* @throws IOException in the event of an IOException
* @throws ServletException in the event of a ServletException
*/
void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException;
}
@@ -1,88 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.AccessDeniedException;
/**
* Base implementation of {@link AccessDeniedHandler}.
* <p>
* This implementation sends a 403 (SC_FORBIDDEN) HTTP error code. In addition, if an {@link #errorPage} is defined,
* the implementation will perform a request dispatcher "forward" to the specified error page view.
* Being a "forward", the <code>SecurityContextHolder</code> will remain
* populated. This is of benefit if the view (or a tag library or macro) wishes to access the
* <code>SecurityContextHolder</code>. The request scope will also be populated with the exception itself, available
* from the key {@link #SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY}.
*
* @author Ben Alex
* @version $Id$
*/
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
//~ Static fields/initializers =====================================================================================
public static final String SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY = "SPRING_SECURITY_403_EXCEPTION";
protected static final Log logger = LogFactory.getLog(AccessDeniedHandlerImpl.class);
//~ Instance fields ================================================================================================
private String errorPage;
//~ Methods ========================================================================================================
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException {
if (!response.isCommitted()) {
if (errorPage != null) {
// Put exception into request scope (perhaps of use to a view)
request.setAttribute(SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY, accessDeniedException);
// Set the 403 status code.
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// forward to error page.
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
}
/**
* The error page to use. Must begin with a "/" and is interpreted relative to the current context root.
*
* @param errorPage the dispatcher path to display
*
* @throws IllegalArgumentException if the argument doesn't comply with the above limitations
*/
public void setErrorPage(String errorPage) {
if ((errorPage != null) && !errorPage.startsWith("/")) {
throw new IllegalArgumentException("errorPage must begin with '/'");
}
this.errorPage = errorPage;
}
}
@@ -1,53 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import org.springframework.security.AuthenticationException;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Used by {@link ExceptionTranslationFilter} to commence an authentication scheme.
*
* @author Ben Alex
* @version $Id$
*/
public interface AuthenticationEntryPoint {
//~ Methods ========================================================================================================
/**
* Commences an authentication scheme.
* <p>
* <code>ExceptionTranslationFilter</code> will populate the <code>HttpSession</code> attribute named
* <code>AbstractProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY</code> with the requested target URL before
* calling this method.
* <p>
* Implementations should modify the headers on the <code>ServletResponse</code> as necessary to
* commence the authentication process.
*
* @param request that resulted in an <code>AuthenticationException</code>
* @param response so that the user agent can begin authentication
* @param authException that caused the invocation
*
*/
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException;
}
@@ -1,34 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.AuthenticationException;
import org.springframework.security.CredentialsExpiredException;
/**
* Strategy used to handle a failed authentication attempt.
* <p>
* Typical behaviour might be to redirect the user to the authentication page (in the case of a form login) to
* allow them to try again. More sophisticated logic might be implemented depending on the type of the exception.
* For example, a {@link CredentialsExpiredException} might cause a redirect to a web controller which allowed the
* user to change their password.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public interface AuthenticationFailureHandler {
/**
* Called when an authentication attempt fails.
* @param request the request during which the authentication attempt occurred.
* @param response the response.
* @param exception the exception which was thrown to reject the authentication request.
*/
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException;
}
@@ -1,36 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.Authentication;
/**
* Strategy used to handle a successful user authentication.
* <p>
* Implementations can do whatever they want but typical behaviour would be to control the navigation to the
* subsequent destination (using a redirect or a forward). For example, after a user has logged in by submitting a
* login form, the application needs to decide where they should be redirected to afterwards
* (see {@link AbstractProcessingFilter} and subclasses). Other logic may also be included if required.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
* @see
*/
public interface AuthenticationSuccessHandler {
/**
* Called when a user has been successfully authenticated.
*
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during the authentication process.
*/
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException;
}
@@ -1,63 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.AuthenticationException;
import org.springframework.security.util.RedirectUtils;
import org.springframework.security.util.UrlUtils;
import org.springframework.util.Assert;
/**
* Uses the internal map of exceptions types to URLs to determine the destination on authentication failure. The keys
* are the full exception class names.
* <p>
* If a match isn't found, falls back to the behaviour of the parent class,
* {@link SimpleUrlAuthenticationFailureHandler}.
* <p>
* The map of exception names to URLs should be injected by setting the <tt>exceptionMappings</tt> property.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class ExceptionMappingAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Map<String, String> failureUrlMap = new HashMap<String, String>();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String url = failureUrlMap.get(exception.getClass().getName());
if (url != null) {
RedirectUtils.sendRedirect(request, response, url, isUseRelativeContext());
} else {
super.onAuthenticationFailure(request, response, exception);
}
}
/**
* Sets the map of exception types (by name) to URLs.
*
* @param failureUrlMap the map keyed by the fully-qualified name of the exception class, with the corresponding
* failure URL as the value.
*
* @throws IllegalArgumentException if the entries are not Strings or the URL is not valid.
*/
public void setExceptionMappings(Map<?,?> failureUrlMap) {
this.failureUrlMap.clear();
for (Map.Entry<?,?> entry : failureUrlMap.entrySet()) {
Object exception = entry.getKey();
Object url = entry.getValue();
Assert.isInstanceOf(String.class, exception, "Exception key must be a String (the exception classname).");
Assert.isInstanceOf(String.class, url, "URL must be a String");
Assert.isTrue(UrlUtils.isValidRedirectUrl((String)url), "Not a valid redirect URL: " + url);
this.failureUrlMap.put((String)exception, (String)url);
}
}
}
@@ -1,271 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.SpringSecurityException;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationTrustResolver;
import org.springframework.security.AuthenticationTrustResolverImpl;
import org.springframework.security.InsufficientAuthenticationException;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ui.savedrequest.SavedRequest;
import org.springframework.security.util.PortResolver;
import org.springframework.security.util.PortResolverImpl;
import org.springframework.security.util.ThrowableAnalyzer;
import org.springframework.security.util.ThrowableCauseExtractor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Handles any <code>AccessDeniedException</code> and <code>AuthenticationException</code> thrown within the
* filter chain.
* <p>
* This filter is necessary because it provides the bridge between Java exceptions and HTTP responses.
* It is solely concerned with maintaining the user interface. This filter does not do any actual security enforcement.
* <p>
* If an {@link AuthenticationException} is detected, the filter will launch the <code>authenticationEntryPoint</code>.
* This allows common handling of authentication failures originating from any subclass of
* {@link org.springframework.security.intercept.AbstractSecurityInterceptor}.
* <p>
* If an {@link AccessDeniedException} is detected, the filter will determine whether or not the user is an anonymous
* user. If they are an anonymous user, the <code>authenticationEntryPoint</code> will be launched. If they are not
* an anonymous user, the filter will delegate to the {@link org.springframework.security.ui.AccessDeniedHandler}.
* By default the filter will use {@link org.springframework.security.ui.AccessDeniedHandlerImpl}.
* <p>
* To use this filter, it is necessary to specify the following properties:
* <ul>
* <li><code>authenticationEntryPoint</code> indicates the handler that
* should commence the authentication process if an
* <code>AuthenticationException</code> is detected. Note that this may also
* switch the current protocol from http to https for an SSL login.</li>
* <li><code>portResolver</code> is used to determine the "real" port that a
* request was received on.</li>
* </ul>
*
* @author Ben Alex
* @author colin sampaleanu
* @version $Id$
*/
public class ExceptionTranslationFilter extends SpringSecurityFilter implements InitializingBean {
//~ Instance fields ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private PortResolver portResolver = new PortResolverImpl();
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private boolean createSessionAllowed = true;
private boolean justUseSavedRequestOnGet;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified");
Assert.notNull(portResolver, "portResolver must be specified");
Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified");
Assert.notNull(throwableAnalyzer, "throwableAnalyzer must be specified");
}
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
try {
chain.doFilter(request, response);
if (logger.isDebugEnabled()) {
logger.debug("Chain processed normally");
}
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
SpringSecurityException ase = (SpringSecurityException)
this.throwableAnalyzer.getFirstThrowableOfType(SpringSecurityException.class, causeChain);
if (ase != null) {
handleException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. These are not expected to happen
throw new RuntimeException(ex);
}
}
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
public PortResolver getPortResolver() {
return portResolver;
}
private void handleException(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
SpringSecurityException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
if (logger.isDebugEnabled()) {
logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
}
sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
if (logger.isDebugEnabled()) {
logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
exception);
}
sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(
"Full authentication is required to access this resource"));
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
}
accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
}
}
}
/**
* If <code>true</code>, indicates that <code>ExceptionTranslationFilter</code> is permitted to store the target
* URL and exception information in a new <code>HttpSession</code> (the default).
* In situations where you do not wish to unnecessarily create <code>HttpSession</code>s - because the user agent
* will know the failed URL, such as with BASIC or Digest authentication - you may wish to set this property to
* <code>false</code>.
* <p>
* Remember to also set
* {@link org.springframework.security.context.HttpSessionSecurityContextRepository#setAllowSessionCreation(boolean)}
* to <code>false</code> if you set this property to <code>false</code>.
*
* @return <code>true</code> if the <code>HttpSession</code> will be
* used to store information about the failed request, <code>false</code>
* if the <code>HttpSession</code> will not be used
*/
public boolean isCreateSessionAllowed() {
return createSessionAllowed;
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
saveRequestIfAllowed(request);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
private void saveRequestIfAllowed(HttpServletRequest request) {
if (!justUseSavedRequestOnGet || "GET".equals(request.getMethod())) {
SavedRequest savedRequest = new SavedRequest(request, portResolver);
if (createSessionAllowed || request.getSession(false) != null) {
// Store the HTTP request itself. Used by AbstractProcessingFilter
// for redirection after successful authentication (SEC-29)
request.getSession().setAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY, savedRequest);
logger.debug("SavedRequest added to Session: " + savedRequest);
}
}
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
this.authenticationTrustResolver = authenticationTrustResolver;
}
public void setCreateSessionAllowed(boolean createSessionAllowed) {
this.createSessionAllowed = createSessionAllowed;
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
this.throwableAnalyzer = throwableAnalyzer;
}
/**
* If <code>true</code>, will only use <code>SavedRequest</code> to determine the target URL on successful
* authentication if the request that caused the authentication request was a GET. Defaults to false.
*/
public void setJustUseSavedRequestOnGet(boolean justUseSavedRequestOnGet) {
this.justUseSavedRequestOnGet = justUseSavedRequestOnGet;
}
public int getOrder() {
return FilterChainOrder.EXCEPTION_TRANSLATION_FILTER;
}
/**
* Default implementation of <code>ThrowableAnalyzer</code> which is capable of also unwrapping
* <code>ServletException</code>s.
*/
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
/**
* @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
*/
protected void initExtractorMap() {
super.initExtractorMap();
registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
return ((ServletException) throwable).getRootCause();
}
});
}
}
}
@@ -1,77 +0,0 @@
package org.springframework.security.ui;
import org.springframework.util.Assert;
import java.util.Map;
import java.util.LinkedHashMap;
/**
* Stores the default order numbers of all Spring Security filters for use in configuration.
*
* @author Luke Taylor
* @version $Id$
*/
public abstract class FilterChainOrder {
/**
* The first position at which a Spring Security filter will be found. Any filter with an order less than this will
* be guaranteed to be placed before the Spring Security filters in the stack.
*/
public static final int FILTER_CHAIN_FIRST = 0;
private static final int INTERVAL = 100;
private static int i = 1;
public static final int CHANNEL_FILTER = FILTER_CHAIN_FIRST;
public static final int CONCURRENT_SESSION_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int SECURITY_CONTEXT_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int HTTP_SESSION_CONTEXT_FILTER = SECURITY_CONTEXT_FILTER;
public static final int LOGOUT_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int X509_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int PRE_AUTH_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int CAS_PROCESSING_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int AUTHENTICATION_PROCESSING_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int OPENID_PROCESSING_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int LOGIN_PAGE_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int DIGEST_PROCESSING_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int BASIC_PROCESSING_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int SERVLET_API_SUPPORT_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int REMEMBER_ME_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int ANONYMOUS_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int EXCEPTION_TRANSLATION_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int NTLM_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int SESSION_FIXATION_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int FILTER_SECURITY_INTERCEPTOR = FILTER_CHAIN_FIRST + INTERVAL * i++;
public static final int SWITCH_USER_FILTER = FILTER_CHAIN_FIRST + INTERVAL * i++;
private static final Map<String, Integer> filterNameToOrder = new LinkedHashMap<String, Integer>();
static {
filterNameToOrder.put("FIRST", new Integer(Integer.MIN_VALUE));
filterNameToOrder.put("CHANNEL_FILTER", new Integer(CHANNEL_FILTER));
filterNameToOrder.put("CONCURRENT_SESSION_FILTER", new Integer(CONCURRENT_SESSION_FILTER));
filterNameToOrder.put("LOGOUT_FILTER", new Integer(LOGOUT_FILTER));
filterNameToOrder.put("X509_FILTER", new Integer(X509_FILTER));
filterNameToOrder.put("PRE_AUTH_FILTER", new Integer(PRE_AUTH_FILTER));
filterNameToOrder.put("CAS_PROCESSING_FILTER", new Integer(CAS_PROCESSING_FILTER));
filterNameToOrder.put("AUTHENTICATION_PROCESSING_FILTER", new Integer(AUTHENTICATION_PROCESSING_FILTER));
filterNameToOrder.put("OPENID_PROCESSING_FILTER", new Integer(OPENID_PROCESSING_FILTER));
filterNameToOrder.put("BASIC_PROCESSING_FILTER", new Integer(BASIC_PROCESSING_FILTER));
filterNameToOrder.put("SERVLET_API_SUPPORT_FILTER", new Integer(SERVLET_API_SUPPORT_FILTER));
filterNameToOrder.put("REMEMBER_ME_FILTER", new Integer(REMEMBER_ME_FILTER));
filterNameToOrder.put("ANONYMOUS_FILTER", new Integer(ANONYMOUS_FILTER));
filterNameToOrder.put("EXCEPTION_TRANSLATION_FILTER", new Integer(EXCEPTION_TRANSLATION_FILTER));
filterNameToOrder.put("NTLM_FILTER", new Integer(NTLM_FILTER));
filterNameToOrder.put("SESSION_CONTEXT_INTEGRATION_FILTER", new Integer(HTTP_SESSION_CONTEXT_FILTER));
filterNameToOrder.put("FILTER_SECURITY_INTERCEPTOR", new Integer(FILTER_SECURITY_INTERCEPTOR));
filterNameToOrder.put("SWITCH_USER_FILTER", new Integer(SWITCH_USER_FILTER));
filterNameToOrder.put("LAST", new Integer(Integer.MAX_VALUE));
}
/** Allows filters to be used by name in the XSD file without explicit reference to Java constants */
public static int getOrder(String filterName) {
Integer order = filterNameToOrder.get(filterName);
Assert.notNull(order, "Unable to match filter name " + filterName);
return order.intValue();
}
}
@@ -1,29 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.Authentication;
import org.springframework.security.ui.logout.LogoutFilter;
/**
* Strategy that is called after a successful logout by the {@link LogoutFilter}, to handle redirection or
* forwarding to the appropriate destination.
* <p>
* Note that the interface is almost the same as {@link LogoutHandler} but may raise an exception.
* <tt>LogoutHandler</tt> implementations expect to be invoked to perform necessary cleanup, so should not throw
* exceptions.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public interface LogoutSuccessHandler {
void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException;
}
@@ -1,94 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.Authentication;
import org.springframework.security.ui.savedrequest.SavedRequest;
import org.springframework.security.util.RedirectUtils;
import org.springframework.security.wrapper.SavedRequestAwareWrapper;
import org.springframework.util.StringUtils;
/**
* An authentication success strategy which can make use of the {@link SavedRequest} which may have been stored in
* the session by the {@link ExceptionTranslationFilter}. When such a request is intercepted and requires authentication,
* the request data is stored to record the original destination before the authentication process commenced, and to
* allow the request to be reconstructed when a redirect to the same URL occurs. This class is responsible for
* performing the redirect to the original URL if appropriate.
* <p>
* Following a successful authentication, it decides on the redirect destination, based on the following scenarios:
* <ul>
* <li>
* If the <tt>alwaysUseDefaultTargetUrl</tt> property is set to true, the <tt>defaultTargetUrl</tt>
* will be used for the destination. Any <tt>SavedRequest</tt> stored in the session will be
* removed.
* </li>
* <li>
* If the <tt>targetUrlParameter</tt> has been set on the request, the value will be used as the destination.
* Any <tt>SavedRequest</tt> will again be removed.
* </li>
* <li>
* If a {@link SavedRequest} is found in the session (as set by the {@link ExceptionTranslationFilter} to record
* the original destination before the authentication process commenced), a redirect will be performed to the
* Url of that original destination. The <tt>SavedRequest</tt> object will remain in the session and be picked up
* when the redirected request is received (See {@link SavedRequestAwareWrapper}).
* </li>
* <li>
* If no <tt>SavedRequest</tt> is found in the session, it will delegate to the base class.
* </li>
* </ul>
*
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
SavedRequest savedRequest = getSavedRequest(request);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
if (isAlwaysUseDefaultTargetUrl() || StringUtils.hasText(request.getParameter(getTargetUrlParameter()))) {
removeSavedRequest(request);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
// Use the SavedRequest URL
String targetUrl = savedRequest.getFullRequestUrl();
logger.debug("Redirecting to SavedRequest Url: " + targetUrl);
RedirectUtils.sendRedirect(request, response, targetUrl, isUseRelativeContext());
}
private SavedRequest getSavedRequest(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
return (SavedRequest) session.getAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
}
return null;
}
private void removeSavedRequest(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
logger.debug("Removing SavedRequest from session if present");
session.removeAttribute(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
}
}
}
@@ -1,97 +0,0 @@
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.HttpSession;
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.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContext;
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);
HttpSession session = request.getSession();
SecurityContext sessionSecurityContext =
(SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
if (sessionSecurityContext == null && isAuthenticated()) {
// The user has been authenticated during the current request, so do the session migration
startNewSessionIfRequired(request, response);
}
chain.doFilter(request, response);
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && !authenticationTrustResolver.isAnonymous(authentication);
}
public void setMigrateSessionAttributes(boolean migrateSessionAttributes) {
this.migrateSessionAttributes = migrateSessionAttributes;
}
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
public int getOrder() {
return FilterChainOrder.SESSION_FIXATION_FILTER;
}
/**
* Called when the a user wasn't authenticated at the start of the request but has been during it
* <p>
* 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, HttpServletResponse response) {
SessionUtils.startNewSessionIfRequired(request, migrateSessionAttributes, sessionRegistry);
}
}
@@ -1,86 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.AuthenticationException;
import org.springframework.security.util.RedirectUtils;
import org.springframework.security.util.UrlUtils;
import org.springframework.util.Assert;
/**
* <tt>AuthenticationFailureHandler</tt> which performs a redirect to the value of the {@link #setDefaultFailureUrl
* defaultFailureUrl} property when the <tt>onAuthenticationFailure</tt> method is called.
* If the property has not been set it will send a 401 response to the client, with the error message from the
* <tt>AuthenticationException</tt> which caused the failure.
* <p>
* If the <tt>forwardToDestination</tt> parameter is set, a <tt>RequestDispatcher.forward</tt> call will be made to
* the destination instead of a redirect.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
private boolean forwardToDestination = false;
private boolean useRelativeContext = false;
public SimpleUrlAuthenticationFailureHandler() {
}
public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
setDefaultFailureUrl(defaultFailureUrl);
}
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (defaultFailureUrl == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
} else {
if (forwardToDestination) {
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
} else {
RedirectUtils.sendRedirect(request, response, defaultFailureUrl, useRelativeContext);
}
}
}
/**
* The URL which will be used as the failure destination.
*
* @param defaultFailureUrl the failure URL, for example "/loginFailed.jsp".
*/
public void setDefaultFailureUrl(String defaultFailureUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl));
this.defaultFailureUrl = defaultFailureUrl;
}
protected boolean isUseForward() {
return forwardToDestination;
}
/**
* If set to <tt>true</tt>, performs a forward to the failure destination URL instead of a redirect. Defaults to
* <tt>false</tt>.
*/
public void setUseForward(boolean forwardToDestination) {
this.forwardToDestination = forwardToDestination;
}
protected boolean isUseRelativeContext() {
return useRelativeContext;
}
/**
* If true, causes any redirection URLs to be calculated minus the protocol
* and context path (defaults to false).
*/
public void setUseRelativeContext(boolean useRelativeContext) {
this.useRelativeContext = useRelativeContext;
}
}
@@ -1,25 +0,0 @@
package org.springframework.security.ui;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.Authentication;
public class SimpleUrlAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler {
public SimpleUrlAuthenticationSuccessHandler() {
}
public SimpleUrlAuthenticationSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
handle(request, response, authentication);
}
}
@@ -1,52 +0,0 @@
package org.springframework.security.ui;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import javax.servlet.FilterChain;
import javax.servlet.ServletResponse;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.Filter;
import java.io.IOException;
/**
* Implements Ordered interface as required by security namespace configuration and implements unused filter
* lifecycle methods and performs casting of request and response to http versions in doFilter method.
*
* @author Luke Taylor
* @version $Id$
*/
public abstract class SpringSecurityFilter implements Filter, Ordered {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
* Does nothing. We use IoC container lifecycle services instead.
*
* @param filterConfig ignored
* @throws ServletException ignored
*/
public final void init(FilterConfig filterConfig) throws ServletException {
}
/**
* Does nothing. We use IoC container lifecycle services instead.
*/
public final void destroy() {
}
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilterHttp((HttpServletRequest)request, (HttpServletResponse)response, chain);
}
protected abstract void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException;
public String toString() {
return getClass().getName() + "[ order=" + getOrder() + "; ]";
}
}
@@ -1,142 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import org.springframework.security.concurrent.SessionIdentifierAware;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* A holder of selected HTTP details related to a web authentication request.
*
* @author Ben Alex
* @version $Id$
*/
public class WebAuthenticationDetails implements SessionIdentifierAware, Serializable {
//~ Instance fields ================================================================================================
private String remoteAddress;
private String sessionId;
//~ Constructors ===================================================================================================
/**
* Records the remote address and will also set the session Id if a session
* already exists (it won't create one).
*
* @param request that the authentication request was received from
*/
public WebAuthenticationDetails(HttpServletRequest request) {
this.remoteAddress = request.getRemoteAddr();
HttpSession session = request.getSession(false);
this.sessionId = (session != null) ? session.getId() : null;
doPopulateAdditionalInformation(request);
}
//~ Methods ========================================================================================================
/**
* Provided so that subclasses can populate additional information.
*
* @param request that the authentication request was received from
*/
protected void doPopulateAdditionalInformation(HttpServletRequest request) {}
public boolean equals(Object obj) {
if (obj instanceof WebAuthenticationDetails) {
WebAuthenticationDetails rhs = (WebAuthenticationDetails) obj;
if ((remoteAddress == null) && (rhs.getRemoteAddress() != null)) {
return false;
}
if ((remoteAddress != null) && (rhs.getRemoteAddress() == null)) {
return false;
}
if (remoteAddress != null) {
if (!remoteAddress.equals(rhs.getRemoteAddress())) {
return false;
}
}
if ((sessionId == null) && (rhs.getSessionId() != null)) {
return false;
}
if ((sessionId != null) && (rhs.getSessionId() == null)) {
return false;
}
if (sessionId != null) {
if (!sessionId.equals(rhs.getSessionId())) {
return false;
}
}
return true;
}
return false;
}
/**
* Indicates the TCP/IP address the authentication request was received from.
*
* @return the address
*/
public String getRemoteAddress() {
return remoteAddress;
}
/**
* Indicates the <code>HttpSession</code> id the authentication request was received from.
*
* @return the session ID
*/
public String getSessionId() {
return sessionId;
}
public int hashCode() {
int code = 7654;
if (this.remoteAddress != null) {
code = code * (this.remoteAddress.hashCode() % 7);
}
if (this.sessionId != null) {
code = code * (this.sessionId.hashCode() % 7);
}
return code;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString() + ": ");
sb.append("RemoteIpAddress: " + this.getRemoteAddress() + "; ");
sb.append("SessionId: " + this.getSessionId());
return sb.toString();
}
}
@@ -1,70 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.http.HttpServletRequest;
/**
* Implementation of {@link AuthenticationDetailsSource} which builds the details object from
* an <tt>HttpServletRequest</tt> object.
* <p>
* By default will create an instance of <code>WebAuthenticationDetails</code>. Any object that accepts a
* <code>HttpServletRequest</code> as its sole constructor can be used instead of this default.
*
* @author Ben Alex
* @version $Id$
*/
public class WebAuthenticationDetailsSource implements AuthenticationDetailsSource {
//~ Instance fields ================================================================================================
private Class<?> clazz = WebAuthenticationDetails.class;
//~ Methods ========================================================================================================
/**
* @param context the <tt>HttpServletRequest</tt> object.
*/
public Object buildDetails(Object context) {
Assert.isInstanceOf(HttpServletRequest.class, context);
try {
Constructor<?> constructor = clazz.getConstructor(HttpServletRequest.class);
return constructor.newInstance(context);
} catch (NoSuchMethodException ex) {
ReflectionUtils.handleReflectionException(ex);
} catch (InvocationTargetException ex) {
ReflectionUtils.handleReflectionException(ex);
} catch (InstantiationException ex) {
ReflectionUtils.handleReflectionException(ex);
} catch (IllegalAccessException ex) {
ReflectionUtils.handleReflectionException(ex);
}
return null;
}
public void setClazz(Class<?> clazz) {
Assert.notNull(clazz, "Class required");
this.clazz = clazz;
}
}
@@ -1,261 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.basicauth;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.WebAuthenticationDetailsSource;
import org.springframework.security.ui.AuthenticationEntryPoint;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.ui.rememberme.NullRememberMeServices;
import org.springframework.security.ui.rememberme.RememberMeServices;
import org.springframework.util.Assert;
/**
* Processes a HTTP request's BASIC authorization headers, putting the result into the
* <code>SecurityContextHolder</code>.
*
* <p>
* For a detailed background on what this filter is designed to process, refer to
* <a href="http://www.faqs.org/rfcs/rfc1945.html">RFC 1945, Section 11.1</a>. Any realm name presented in
* the HTTP request is ignored.
*
* <p>
* In summary, this filter is responsible for processing any request that has a HTTP request header of
* <code>Authorization</code> with an authentication scheme of <code>Basic</code> and a Base64-encoded
* <code>username:password</code> token. For example, to authenticate user "Aladdin" with password "open sesame" the
* following header would be presented:
* <pre>
*
* Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
* </pre>
*
* <p>
* This filter can be used to provide BASIC authentication services to both remoting protocol clients (such as
* Hessian and SOAP) as well as standard user agents (such as Internet Explorer and Netscape).
* <p>
* If authentication is successful, the resulting {@link Authentication} object will be placed into the
* <code>SecurityContextHolder</code>.
*
* <p>
* If authentication fails and <code>ignoreFailure</code> is <code>false</code> (the default), an {@link
* AuthenticationEntryPoint} implementation is called (unless the <tt>ignoreFailure</tt> property is set to
* <tt>true</tt>). Usually this should be {@link BasicProcessingFilterEntryPoint}, which will prompt the user to
* authenticate again via BASIC authentication.
*
* <p>
* Basic authentication is an attractive protocol because it is simple and widely deployed. However, it still
* transmits a password in clear text and as such is undesirable in many situations. Digest authentication is also
* provided by Spring Security and should be used instead of Basic authentication wherever possible. See {@link
* org.springframework.security.ui.digestauth.DigestProcessingFilter}.
* <p>
* Note that if a {@link RememberMeServices} is set, this filter will automatically send back remember-me
* details to the client. Therefore, subsequent requests will not need to present a BASIC authentication header as
* they will be authenticated using the remember-me mechanism.
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilter extends SpringSecurityFilter implements InitializingBean {
//~ Instance fields ================================================================================================
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private boolean ignoreFailure = false;
private String credentialsCharset = "UTF-8";
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
if(!isIgnoreFailure()) {
Assert.notNull(this.authenticationEntryPoint, "An AuthenticationEntryPoint is required");
}
}
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (logger.isDebugEnabled()) {
logger.debug("Authorization header: " + header);
}
if ((header != null) && header.startsWith("Basic ")) {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
String token = new String(Base64.decodeBase64(base64Token), getCredentialsCharset(request));
String username = "";
String password = "";
int delim = token.indexOf(":");
if (delim != -1) {
username = token.substring(0, delim);
password = token.substring(delim + 1);
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
Authentication authResult;
try {
authResult = authenticationManager.authenticate(authRequest);
} catch (AuthenticationException failed) {
// Authentication failed
if (logger.isDebugEnabled()) {
logger.debug("Authentication request for user: " + username + " failed: " + failed.toString());
}
SecurityContextHolder.getContext().setAuthentication(null);
rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (ignoreFailure) {
chain.doFilter(request, response);
} else {
authenticationEntryPoint.commence(request, response, failed);
}
return;
}
// Authentication success
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult.toString());
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
chain.doFilter(request, response);
}
private boolean authenticationIsRequired(String username) {
// Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated
// (see SEC-53)
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if(existingAuth == null || !existingAuth.isAuthenticated()) {
return true;
}
// Limit username comparison to providers which use usernames (ie UsernamePasswordAuthenticationToken)
// (see SEC-348)
if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
return true;
}
// Handle unusual condition where an AnonymousAuthenticationToken is already present
// This shouldn't happen very often, as BasicProcessingFitler is meant to be earlier in the filter
// chain than AnonymousProcessingFilter. Nevertheless, presence of both an AnonymousAuthenticationToken
// together with a BASIC authentication request header should indicate reauthentication using the
// BASIC protocol is desirable. This behaviour is also consistent with that provided by form and digest,
// both of which force re-authentication if the respective header is detected (and in doing so replace
// any existing AnonymousAuthenticationToken). See SEC-610.
if (existingAuth instanceof AnonymousAuthenticationToken) {
return true;
}
return false;
}
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException {
}
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException {
}
protected AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
protected boolean isIgnoreFailure() {
return ignoreFailure;
}
public void setIgnoreFailure(boolean ignoreFailure) {
this.ignoreFailure = ignoreFailure;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.rememberMeServices = rememberMeServices;
}
public void setCredentialsCharset(String credentialsCharset) {
Assert.hasText(credentialsCharset, "credentialsCharset cannot be null or empty");
this.credentialsCharset = credentialsCharset;
}
protected String getCredentialsCharset(HttpServletRequest httpRequest) {
return credentialsCharset;
}
public int getOrder() {
return FilterChainOrder.BASIC_PROCESSING_FILTER;
}
}
@@ -1,66 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.basicauth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.AuthenticationException;
import org.springframework.security.ui.AuthenticationEntryPoint;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Used by the <code>SecurityEnforcementFilter</code> to commence authentication via the {@link
* BasicProcessingFilter}.<P>Once a user agent is authenticated using BASIC authentication, logout requires that
* the browser be closed or an unauthorized (401) header be sent. The simplest way of achieving the latter is to call
* the {@link #commence(HttpServletRequest, HttpServletResponse, AuthenticationException)} method below. This will indicate to
* the browser its credentials are no longer authorized, causing it to prompt the user to login again.</p>
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean {
//~ Instance fields ================================================================================================
private String realmName;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.hasText(realmName, "realmName must be specified");
}
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
public String getRealmName() {
return realmName;
}
public void setRealmName(String realmName) {
this.realmName = realmName;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Authenticates HTTP BASIC authentication requests.
</body>
</html>
@@ -1,419 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.digestauth;
import java.io.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationServiceException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.dao.UserCache;
import org.springframework.security.providers.dao.cache.NullUserCache;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.ui.WebAuthenticationDetailsSource;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.util.StringSplitUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Processes a HTTP request's Digest authorization headers, putting the result into the
* <code>SecurityContextHolder</code>.
* <p>
* For a detailed background on what this filter is designed to process, refer to
* <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a> (which superseded RFC 2069, although this
* filter support clients that implement either RFC 2617 or RFC 2069).
* <p>
* This filter can be used to provide Digest authentication services to both remoting protocol clients (such as
* Hessian and SOAP) as well as standard user agents (such as Internet Explorer and FireFox).
* <p>
* This Digest implementation has been designed to avoid needing to store session state between invocations.
* All session management information is stored in the "nonce" that is sent to the client by the {@link
* DigestProcessingFilterEntryPoint}.
* <p>
* If authentication is successful, the resulting {@link org.springframework.security.Authentication Authentication}
* object will be placed into the <code>SecurityContextHolder</code>.
* <p>
* If authentication fails, an {@link org.springframework.security.ui.AuthenticationEntryPoint AuthenticationEntryPoint}
* implementation is called. This must always be {@link DigestProcessingFilterEntryPoint}, which will prompt the user
* to authenticate again via Digest authentication.
* <p>
* Note there are limitations to Digest authentication, although it is a more comprehensive and secure solution
* than Basic authentication. Please see RFC 2617 section 4 for a full discussion on the advantages of Digest
* authentication over Basic authentication, including commentary on the limitations that it still imposes.
*/
public class DigestProcessingFilter extends SpringSecurityFilter implements Filter, InitializingBean, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(DigestProcessingFilter.class);
//~ Instance fields ================================================================================================
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private DigestProcessingFilterEntryPoint authenticationEntryPoint;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private UserDetailsService userDetailsService;
private boolean passwordAlreadyEncoded = false;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(userDetailsService, "A UserDetailsService is required");
Assert.notNull(authenticationEntryPoint, "A DigestProcessingFilterEntryPoint is required");
}
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (logger.isDebugEnabled()) {
logger.debug("Authorization header received from user agent: " + header);
}
if ((header != null) && header.startsWith("Digest ")) {
String section212response = header.substring(7);
String[] headerEntries = StringSplitUtils.splitIgnoringQuotes(section212response, ',');
Map<String,String> headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
String username = (String) headerMap.get("username");
String realm = (String) headerMap.get("realm");
String nonce = (String) headerMap.get("nonce");
String uri = (String) headerMap.get("uri");
String responseDigest = (String) headerMap.get("response");
String qop = (String) headerMap.get("qop"); // RFC 2617 extension
String nc = (String) headerMap.get("nc"); // RFC 2617 extension
String cnonce = (String) headerMap.get("cnonce"); // RFC 2617 extension
// Check all required parameters were supplied (ie RFC 2069)
if ((username == null) || (realm == null) || (nonce == null) || (uri == null) || (response == null)) {
if (logger.isDebugEnabled()) {
logger.debug("extracted username: '" + username + "'; realm: '" + username + "'; nonce: '"
+ username + "'; uri: '" + username + "'; response: '" + username + "'");
}
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingMandatory",
new Object[]{section212response}, "Missing mandatory digest value; received header {0}")));
return;
}
// Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
if ("auth".equals(qop)) {
if ((nc == null) || (cnonce == null)) {
if (logger.isDebugEnabled()) {
logger.debug("extracted nc: '" + nc + "'; cnonce: '" + cnonce + "'");
}
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.missingAuth",
new Object[]{section212response}, "Missing mandatory digest value; received header {0}")));
return;
}
}
// Check realm name equals what we expected
if (!this.getAuthenticationEntryPoint().getRealmName().equals(realm)) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectRealm",
new Object[]{realm, this.getAuthenticationEntryPoint().getRealmName()},
"Response realm name '{0}' does not match system realm name of '{1}'")));
return;
}
// Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint)
if (!Base64.isArrayByteBase64(nonce.getBytes())) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceEncoding",
new Object[]{nonce}, "Nonce is not encoded in Base64; received nonce {0}")));
return;
}
// Decode nonce from Base64
// format of nonce is:
// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
String nonceAsPlainText = new String(Base64.decodeBase64(nonce.getBytes()));
String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
if (nonceTokens.length != 2) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotTwoTokens",
new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}")));
return;
}
// Extract expiry time from nonce
long nonceExpiryTime;
try {
nonceExpiryTime = new Long(nonceTokens[0]).longValue();
} catch (NumberFormatException nfe) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceNotNumeric",
new Object[]{nonceAsPlainText},
"Nonce token should have yielded a numeric first token, but was {0}")));
return;
}
// Check signature of nonce matches this expiry time
String expectedNonceSignature = DigestUtils.md5Hex(nonceExpiryTime + ":"
+ this.getAuthenticationEntryPoint().getKey());
if (!expectedNonceSignature.equals(nonceTokens[1])) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.nonceCompromised",
new Object[]{nonceAsPlainText}, "Nonce token compromised {0}")));
return;
}
// Lookup password for presented username
// NB: DAO-provided password MUST be clear text - not encoded/salted
// (unless this instance's passwordAlreadyEncoded property is 'false')
boolean loadedFromDao = false;
UserDetails user = userCache.getUserFromCache(username);
if (user == null) {
loadedFromDao = true;
try {
user = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound",
new Object[]{username}, "Username {0} not found")));
return;
}
if (user == null) {
throw new AuthenticationServiceException(
"AuthenticationDao returned null, which is an interface contract violation");
}
userCache.putUserInCache(user);
}
// Compute the expected response-digest (will be in hex form)
String serverDigestMd5;
// Don't catch IllegalArgumentException (already checked validity)
serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
((HttpServletRequest) request).getMethod(), uri, qop, nonce, nc, cnonce);
// If digest is incorrect, try refreshing from backend and recomputing
if (!serverDigestMd5.equals(responseDigest) && !loadedFromDao) {
if (logger.isDebugEnabled()) {
logger.debug(
"Digest comparison failure; trying to refresh user from DAO in case password had changed");
}
try {
user = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
// Would very rarely happen, as user existed earlier
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.usernameNotFound",
new Object[]{username}, "Username {0} not found")));
}
userCache.putUserInCache(user);
// Don't catch IllegalArgumentException (already checked validity)
serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm, user.getPassword(),
((HttpServletRequest) request).getMethod(), uri, qop, nonce, nc, cnonce);
}
// If digest is still incorrect, definitely reject authentication attempt
if (!serverDigestMd5.equals(responseDigest)) {
if (logger.isDebugEnabled()) {
logger.debug("Expected response: '" + serverDigestMd5 + "' but received: '" + responseDigest
+ "'; is AuthenticationDao returning clear text passwords?");
}
fail(request, response,
new BadCredentialsException(messages.getMessage("DigestProcessingFilter.incorrectResponse",
"Incorrect response")));
return;
}
// To get this far, the digest must have been valid
// Check the nonce has not expired
// We do this last so we can direct the user agent its nonce is stale
// but the request was otherwise appearing to be valid
if (nonceExpiryTime < System.currentTimeMillis()) {
fail(request, response,
new NonceExpiredException(messages.getMessage("DigestProcessingFilter.nonceExpired",
"Nonce has expired/timed out")));
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Authentication success for user: '" + username + "' with response: '" + responseDigest
+ "'");
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user,
user.getPassword());
authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request, response);
}
public static String encodePasswordInA1Format(String username, String realm, String password) {
String a1 = username + ":" + realm + ":" + password;
String a1Md5 = new String(DigestUtils.md5Hex(a1));
return a1Md5;
}
private void fail(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(null);
if (logger.isDebugEnabled()) {
logger.debug(failed);
}
authenticationEntryPoint.commence(request, response, failed);
}
/**
* Computes the <code>response</code> portion of a Digest authentication header. Both the server and user
* agent should compute the <code>response</code> independently. Provided as a static method to simplify the
* coding of user agents.
*
* @param passwordAlreadyEncoded true if the password argument is already encoded in the correct format. False if
* it is plain text.
* @param username the user's login name.
* @param realm the name of the realm.
* @param password the user's password in plaintext or ready-encoded.
* @param httpMethod the HTTP request method (GET, POST etc.)
* @param uri the request URI.
* @param qop the qop directive, or null if not set.
* @param nonce the nonce supplied by the server
* @param nc the "nonce-count" as defined in RFC 2617.
* @param cnonce opaque string supplied by the client when qop is set.
* @return the MD5 of the digest authentication response, encoded in hex
* @throws IllegalArgumentException if the supplied qop value is unsupported.
*/
public static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password,
String httpMethod, String uri, String qop, String nonce, String nc, String cnonce)
throws IllegalArgumentException {
String a1Md5 = null;
String a2 = httpMethod + ":" + uri;
String a2Md5 = new String(DigestUtils.md5Hex(a2));
if (passwordAlreadyEncoded) {
a1Md5 = password;
} else {
a1Md5 = encodePasswordInA1Format(username, realm, password);
}
String digest;
if (qop == null) {
// as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
digest = a1Md5 + ":" + nonce + ":" + a2Md5;
} else if ("auth".equals(qop)) {
// As per RFC 2617 compliant clients
digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
} else {
throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
}
String digestMd5 = new String(DigestUtils.md5Hex(digest));
return digestMd5;
}
public DigestProcessingFilterEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public UserCache getUserCache() {
return userCache;
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setAuthenticationEntryPoint(DigestProcessingFilterEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setPasswordAlreadyEncoded(boolean passwordAlreadyEncoded) {
this.passwordAlreadyEncoded = passwordAlreadyEncoded;
}
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public int getOrder() {
return FilterChainOrder.DIGEST_PROCESSING_FILTER;
}
}
@@ -1,130 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.digestauth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.AuthenticationException;
import org.springframework.security.ui.AuthenticationEntryPoint;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
/**
* Used by the <code>SecurityEnforcementFilter</code> to commence authentication via the {@link
* DigestProcessingFilter}.<p>The nonce sent back to the user agent will be valid for the period indicated by
* {@link #setNonceValiditySeconds(int)}. By default this is 300 seconds. Shorter times should be used if replay
* attacks are a major concern. Larger values can be used if performance is a greater concern. This class correctly
* presents the <code>stale=true</code> header when the nonce has expierd, so properly implemented user agents will
* automatically renegotiate with a new nonce value (ie without presenting a new password dialog box to the user).</p>
*
* @author Ben Alex
* @version $Id$
*/
public class DigestProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean, Ordered {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(DigestProcessingFilterEntryPoint.class);
//~ Instance fields ================================================================================================
private String key;
private String realmName;
private int nonceValiditySeconds = 300;
private int order = Integer.MAX_VALUE; // ~ default
//~ Methods ========================================================================================================
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public void afterPropertiesSet() throws Exception {
if ((realmName == null) || "".equals(realmName)) {
throw new IllegalArgumentException("realmName must be specified");
}
if ((key == null) || "".equals(key)) {
throw new IllegalArgumentException("key must be specified");
}
}
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// compute a nonce (do not use remote IP address due to proxy farms)
// format of nonce is:
// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000);
String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":" + key));
String nonceValue = expiryTime + ":" + signatureValue;
String nonceValueBase64 = new String(Base64.encodeBase64(nonceValue.getBytes()));
// qop is quality of protection, as defined by RFC 2617.
// we do not use opaque due to IE violation of RFC 2617 in not
// representing opaque on subsequent requests in same session.
String authenticateHeader = "Digest realm=\"" + realmName + "\", " + "qop=\"auth\", nonce=\""
+ nonceValueBase64 + "\"";
if (authException instanceof NonceExpiredException) {
authenticateHeader = authenticateHeader + ", stale=\"true\"";
}
if (logger.isDebugEnabled()) {
logger.debug("WWW-Authenticate header sent to user agent: " + authenticateHeader);
}
httpResponse.addHeader("WWW-Authenticate", authenticateHeader);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
public String getKey() {
return key;
}
public int getNonceValiditySeconds() {
return nonceValiditySeconds;
}
public String getRealmName() {
return realmName;
}
public void setKey(String key) {
this.key = key;
}
public void setNonceValiditySeconds(int nonceValiditySeconds) {
this.nonceValiditySeconds = nonceValiditySeconds;
}
public void setRealmName(String realmName) {
this.realmName = realmName;
}
}
@@ -1,50 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.digestauth;
import org.springframework.security.AuthenticationException;
/**
* Thrown if an authentication request is rejected because the digest nonce has expired.
*
* @author Ben Alex
* @version $Id$
*/
public class NonceExpiredException extends AuthenticationException {
//~ Constructors ===================================================================================================
/**
* Constructs a <code>NonceExpiredException</code> with the specified
* message.
*
* @param msg the detail message
*/
public NonceExpiredException(String msg) {
super(msg);
}
/**
* Constructs a <code>NonceExpiredException</code> with the specified
* message and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public NonceExpiredException(String msg, Throwable t) {
super(msg, t);
}
}
@@ -1,5 +0,0 @@
<html>
<body>
Authenticates HTTP Digest authentication requests.
</body>
</html>
@@ -1,150 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.logout;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.LogoutSuccessHandler;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Logs a principal out.
* <p>
* Polls a series of {@link LogoutHandler}s. The handlers should be specified in the order they are required.
* Generally you will want to call logout handlers <code>TokenBasedRememberMeServices</code> and
* <code>SecurityContextLogoutHandler</code> (in that order).
* <p>
* After logout, a redirect will be performed to the URL determined by either the configured
* <tt>LogoutSuccessHandler</tt> or the <tt>logoutSuccessUrl</tt>, depending on which constructor was used.
*
* @author Ben Alex
* @version $Id$
*/
public class LogoutFilter extends SpringSecurityFilter {
//~ Instance fields ================================================================================================
private String filterProcessesUrl = "/j_spring_security_logout";
private List<LogoutHandler> handlers; private LogoutSuccessHandler logoutSuccessHandler;
//~ Constructors ===================================================================================================
/**
* Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the target destination
* after logging out. The list of <tt>LogoutHandler</tt>s are intended to perform the actual logout functionality
* (such as clearing the security context, invalidating the session, etc.).
*/
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
Assert.notEmpty(handlers, "LogoutHandlers are required");
this.handlers = Arrays.asList(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
}
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
Assert.notEmpty(handlers, "LogoutHandlers are required");
this.handlers = Arrays.asList(handlers);
Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) ||
UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if (StringUtils.hasText(logoutSuccessUrl)) {
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
}
logoutSuccessHandler = urlLogoutSuccessHandler;
}
//~ Methods ========================================================================================================
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
}
for (LogoutHandler handler : handlers) {
handler.logout(request, response, auth);
}
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
/**
* Allow subclasses to modify when a logout should take place.
*
* @param request the request
* @param response the response
*
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
*/
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) {
// strip everything from the first semi-colon
uri = uri.substring(0, pathParamIndex);
}
int queryParamIndex = uri.indexOf('?');
if (queryParamIndex > 0) {
// strip everything from the first question mark
uri = uri.substring(0, queryParamIndex);
}
if ("".equals(request.getContextPath())) {
return uri.endsWith(filterProcessesUrl);
}
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid value for" +
" 'filterProcessesUrl'");
this.filterProcessesUrl = filterProcessesUrl;
}
protected String getFilterProcessesUrl() {
return filterProcessesUrl;
}
public int getOrder() {
return FilterChainOrder.LOGOUT_FILTER;
}
}
@@ -1,44 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.logout;
import org.springframework.security.Authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Indicates a class that is able to participate in logout handling.
*
* <p>
* Called by {@link LogoutFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public interface LogoutHandler {
//~ Methods ========================================================================================================
/**
* Causes a logout to be completed. The method must complete successfully.
*
* @param request the HTTP request
* @param response the HTTP response
* @param authentication the current principal details
*/
void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
}
@@ -1,74 +0,0 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.security.ui.logout;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Performs a logout by modifying the {@link org.springframework.security.context.SecurityContextHolder}.
* <p>
* Will also invalidate the {@link HttpSession} if {@link #isInvalidateHttpSession()} is <code>true</code> and the
* session is not <code>null</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class SecurityContextLogoutHandler implements LogoutHandler {
private boolean invalidateHttpSession = true;
//~ Methods ========================================================================================================
/**
* Requires the request to be passed in.
*
* @param request from which to obtain a HTTP session (cannot be null)
* @param response not used (can be <code>null</code>)
* @param authentication not used (can be <code>null</code>)
*/
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
SecurityContextHolder.clearContext();
}
public boolean isInvalidateHttpSession() {
return invalidateHttpSession;
}
/**
* Causes the {@link HttpSession} to be invalidated when this {@link LogoutHandler} is invoked. Defaults to true.
*
* @param invalidateHttpSession true if you wish the session to be invalidated (default) or false if it should
* not be.
*/
public void setInvalidateHttpSession(boolean invalidateHttpSession) {
this.invalidateHttpSession = invalidateHttpSession;
}
}
@@ -1,29 +0,0 @@
package org.springframework.security.ui.logout;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.Authentication;
import org.springframework.security.ui.AbstractAuthenticationTargetUrlRequestHandler;
import org.springframework.security.ui.LogoutSuccessHandler;
/**
* Handles the navigation on logout by delegating to the {@link AbstractAuthenticationTargetUrlRequestHandler}
* base class logic.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public class SimpleUrlLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
super.handle(request, response, authentication);
}
}
@@ -1,6 +0,0 @@
<html>
<body>
Authentication processing mechanisms, which respond to the submission of authentication
credentials using various protocols (eg BASIC, CAS, form login etc).
</body>
</html>
@@ -1,174 +0,0 @@
package org.springframework.security.ui.preauth;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.event.authentication.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.WebAuthenticationDetailsSource;
import org.springframework.security.ui.AbstractProcessingFilter;
import org.springframework.security.ui.SpringSecurityFilter;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.Assert;
/**
* Base class for processing filters that handle pre-authenticated authentication requests. Subclasses must implement
* the getPreAuthenticatedPrincipal() and getPreAuthenticatedCredentials() methods.
* <p>
* By default, the filter chain will proceed when an authentication attempt fails in order to allow other
* authentication mechanisms to process the request. To reject the credentials immediately, set the
* <tt>continueFilterChainOnUnsuccessfulAuthentication</tt> flag to false. The exception raised by the
* <tt>AuthenticationManager</tt> will the be re-thrown. Note that this will not affect cases where the principal
* returned by {@link #getPreAuthenticatedPrincipal} is null, when the chain will still proceed as normal.
*
*
* @author Luke Taylor
* @author Ruud Senden
* @since 2.0
*/
public abstract class AbstractPreAuthenticatedProcessingFilter extends SpringSecurityFilter implements
InitializingBean, ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher = null;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager = null;
private boolean continueFilterChainOnUnsuccessfulAuthentication = true;
/**
* Check whether all required properties have been set.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(authenticationManager, "An AuthenticationManager must be set");
}
/**
* Try to authenticate a pre-authenticated user with Spring Security if the user has not yet been authenticated.
*/
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Checking secure context token: " + SecurityContextHolder.getContext().getAuthentication());
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
doAuthenticate(request, response);
}
filterChain.doFilter(request, response);
}
/**
* Do the actual authentication for a pre-authenticated user.
*/
private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
Authentication authResult = null;
Object principal = getPreAuthenticatedPrincipal(request);
Object credentials = getPreAuthenticatedCredentials(request);
if (principal == null) {
if (logger.isDebugEnabled()) {
logger.debug("No pre-authenticated principal found in request");
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("preAuthenticatedPrincipal = " + principal + ", trying to authenticate");
}
try {
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(principal, credentials);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
authResult = authenticationManager.authenticate(authRequest);
successfulAuthentication(request, response, authResult);
} catch (AuthenticationException failed) {
unsuccessfulAuthentication(request, response, failed);
if (!continueFilterChainOnUnsuccessfulAuthentication) {
throw failed;
}
}
}
/**
* Puts the <code>Authentication</code> instance returned by the
* authentication manager into the secure context.
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
}
/**
* Ensures the authentication object in the secure context is set to null
* when authentication fails.
*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Cleared security context due to exception", failed);
}
request.getSession().setAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}
/**
* @param anApplicationEventPublisher
* The ApplicationEventPublisher to use
*/
public void setApplicationEventPublisher(ApplicationEventPublisher anApplicationEventPublisher) {
this.eventPublisher = anApplicationEventPublisher;
}
/**
* @param authenticationDetailsSource
* The AuthenticationDetailsSource to use
*/
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
/**
* @param authenticationManager
* The AuthenticationManager to use
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setContinueFilterChainOnUnsuccessfulAuthentication(boolean shouldContinue) {
continueFilterChainOnUnsuccessfulAuthentication = shouldContinue;
}
/**
* Override to extract the principal information from the current request
*/
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
/**
* Override to extract the credentials (if applicable) from the current request. Some implementations
* may return a dummy value.
*/
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
}
@@ -1,11 +0,0 @@
package org.springframework.security.ui.preauth;
import org.springframework.security.AuthenticationException;
public class PreAuthenticatedCredentialsNotFoundException extends AuthenticationException {
public PreAuthenticatedCredentialsNotFoundException(String msg) {
super(msg);
}
}
@@ -1,54 +0,0 @@
package org.springframework.security.ui.preauth;
import java.util.Collections;
import java.util.List;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.MutableGrantedAuthoritiesContainer;
import org.springframework.security.ui.AuthenticationDetails;
import org.springframework.util.Assert;
/**
* This AuthenticationDetails implementation allows for storing a list of
* pre-authenticated Granted Authorities.
*
* @author Ruud Senden
* @since 2.0
*/
public class PreAuthenticatedGrantedAuthoritiesAuthenticationDetails extends AuthenticationDetails implements
MutableGrantedAuthoritiesContainer {
public static final long serialVersionUID = 1L;
private List<GrantedAuthority> preAuthenticatedGrantedAuthorities = null;
public PreAuthenticatedGrantedAuthoritiesAuthenticationDetails(Object context) {
super(context);
}
/**
* @return The String representation of this object.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString() + "; ");
sb.append("preAuthenticatedGrantedAuthorities: " + preAuthenticatedGrantedAuthorities);
return sb.toString();
}
/**
*
* @see org.springframework.security.GrantedAuthoritiesContainer#getGrantedAuthorities()
*/
public List<GrantedAuthority> getGrantedAuthorities() {
Assert.notNull(preAuthenticatedGrantedAuthorities, "Pre-authenticated granted authorities have not been set");
return preAuthenticatedGrantedAuthorities;
}
/**
* @see org.springframework.security.MutableGrantedAuthoritiesContainer#setGrantedAuthorities()
*/
public void setGrantedAuthorities(List<GrantedAuthority> aJ2eeBasedGrantedAuthorities) {
this.preAuthenticatedGrantedAuthorities = Collections.unmodifiableList(aJ2eeBasedGrantedAuthorities);
}
}
@@ -1,44 +0,0 @@
package org.springframework.security.ui.preauth;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.ui.WebAuthenticationDetails;
import org.springframework.security.GrantedAuthoritiesContainerImpl;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.MutableGrantedAuthoritiesContainer;
/**
* This WebAuthenticationDetails implementation allows for storing a list of
* pre-authenticated Granted Authorities.
*
* @author Ruud Senden
* @author Luke Taylor
* @since 2.0
*/
public class PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails extends WebAuthenticationDetails implements
MutableGrantedAuthoritiesContainer {
public static final long serialVersionUID = 1L;
private MutableGrantedAuthoritiesContainer authoritiesContainer = new GrantedAuthoritiesContainerImpl();
public PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails(HttpServletRequest request) {
super(request);
}
public List<GrantedAuthority> getGrantedAuthorities() {
return authoritiesContainer.getGrantedAuthorities();
}
public void setGrantedAuthorities(List<GrantedAuthority> authorities) {
this.authoritiesContainer.setGrantedAuthorities(authorities);
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString() + "; ");
sb.append(authoritiesContainer);
return sb.toString();
}
}
@@ -1,65 +0,0 @@
package org.springframework.security.ui.preauth;
import org.springframework.security.AuthenticationException;
import org.springframework.security.ui.AuthenticationEntryPoint;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
/**
* <p>
* In the pre-authenticated authentication case (unlike CAS, for example) the
* user will already have been identified through some external mechanism and a
* secure context established by the time the security-enforcement filter is
* invoked.
* <p>
* Therefore this class isn't actually responsible for the commencement of
* authentication, as it is in the case of other providers. It will be called if
* the user is rejected by the AbstractPreAuthenticatedProcessingFilter,
* resulting in a null authentication.
* <p>
* The <code>commence</code> method will always return an
* <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error).
* <p>
* This code is based on
* {@link org.springframework.security.ui.x509.X509ProcessingFilterEntryPoint}.
*
* @see org.springframework.security.ui.ExceptionTranslationFilter
*
* @author Luke Taylor
* @author Ruud Senden
* @since 2.0
*/
public class PreAuthenticatedProcessingFilterEntryPoint implements AuthenticationEntryPoint, Ordered {
private static final Log logger = LogFactory.getLog(PreAuthenticatedProcessingFilterEntryPoint.class);
private int order = Integer.MAX_VALUE;
/**
* Always returns a 403 error code to the client.
*/
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Pre-authenticated entry point called. Rejecting access");
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
}
public int getOrder() {
return order;
}
public void setOrder(int i) {
order = i;
}
}
@@ -1,76 +0,0 @@
package org.springframework.security.ui.preauth.header;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.ui.FilterChainOrder;
import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.ui.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.util.Assert;
/**
* A simple pre-authenticated filter which obtains the username from a request header, for use with systems such as
* CA Siteminder.
* <p>
* As with most pre-authenticated scenarios, it is essential that the external authentication system is set up
* correctly as this filter does no authentication whatsoever. All the protection is assumed to be provided externally
* and if this filter is included inappropriately in a configuration, it would be possible to assume the
* identity of a user merely by setting the correct header name. This also means it should not be used in combination
* with other Spring Security authentication mechanisms such as form login, as this would imply there was a means of
* bypassing the external system which would be risky.
* <p>
* The property <tt>principalRequestHeader</tt> is the name of the request header that contains the username. It
* defaults to "SM_USER" for compatibility with Siteminder.
*
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class RequestHeaderPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader = "SM_USER";
private String credentialsRequestHeader;
/**
* Read and returns the header named by <tt>principalRequestHeader</tt> from the request.
*
* @throws PreAuthenticatedCredentialsNotFoundException if the header is missing
*/
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = request.getHeader(principalRequestHeader);
if (principal == null) {
throw new PreAuthenticatedCredentialsNotFoundException(principalRequestHeader
+ " header not found in request.");
}
return principal;
}
/**
* Credentials aren't usually applicable, but if a <tt>credentialsRequestHeader</tt> is set, this
* will be read and used as the credentials value. Otherwise a dummy value will be used.
*/
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if (credentialsRequestHeader != null) {
String credentials = request.getHeader(credentialsRequestHeader);
return credentials;
}
return "N/A";
}
public void setPrincipalRequestHeader(String principalRequestHeader) {
Assert.hasText(principalRequestHeader, "principalRequestHeader must not be empty or null");
this.principalRequestHeader = principalRequestHeader;
}
public void setCredentialsRequestHeader(String credentialsRequestHeader) {
Assert.hasText(credentialsRequestHeader, "credentialsRequestHeader must not be empty or null");
this.credentialsRequestHeader = credentialsRequestHeader;
}
public int getOrder() {
return FilterChainOrder.PRE_AUTH_FILTER;
}
}
@@ -1,92 +0,0 @@
package org.springframework.security.ui.preauth.j2ee;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.MutableGrantedAuthoritiesContainer;
import org.springframework.security.authoritymapping.Attributes2GrantedAuthoritiesMapper;
import org.springframework.security.authoritymapping.MappableAttributesRetriever;
import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper;
import org.springframework.security.ui.AuthenticationDetailsSourceImpl;
import org.springframework.util.Assert;
/**
* Base implementation for classes scenarios where the authentication details object is used
* to store a list of authorities obtained from the context object (such as an HttpServletRequest)
* passed to {@link #buildDetails(Object)}.
* <p>
*
*
* @author Luke Taylor
* @since 2.0
*/
public abstract class AbstractPreAuthenticatedAuthenticationDetailsSource extends AuthenticationDetailsSourceImpl {
protected final Log logger = LogFactory.getLog(getClass());
protected Set<String> j2eeMappableRoles;
protected Attributes2GrantedAuthoritiesMapper j2eeUserRoles2GrantedAuthoritiesMapper =
new SimpleAttributes2GrantedAuthoritiesMapper();
public AbstractPreAuthenticatedAuthenticationDetailsSource() {
}
/**
* Check that all required properties have been set.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(j2eeMappableRoles, "No mappable roles available");
Assert.notNull(j2eeUserRoles2GrantedAuthoritiesMapper, "Roles to granted authorities mapper not set");
}
/**
* Build the authentication details object. If the specified authentication
* details class implements {@link MutableGrantedAuthoritiesContainer}, a
* list of pre-authenticated Granted Authorities will be set based on the
* roles for the current user.
*
* @see org.springframework.security.ui.AuthenticationDetailsSource#buildDetails(Object)
*/
public Object buildDetails(Object context) {
Object result = super.buildDetails(context);
if (result instanceof MutableGrantedAuthoritiesContainer) {
Collection<String> j2eeUserRoles = getUserRoles(context, j2eeMappableRoles);
List<GrantedAuthority> userGas = j2eeUserRoles2GrantedAuthoritiesMapper.getGrantedAuthorities(j2eeUserRoles);
if (logger.isDebugEnabled()) {
logger.debug("J2EE roles [" + j2eeUserRoles + "] mapped to Granted Authorities: [" + userGas + "]");
}
((MutableGrantedAuthoritiesContainer) result).setGrantedAuthorities(userGas);
}
return result;
}
/**
* Allows the roles of the current user to be determined from the context object
*
* @param context the context object (an HttpRequest, PortletRequest etc)
* @param mappableRoles the possible roles as determined by the MappableAttributesRetriever
* @return the subset of mappable roles which the current user has.
*/
protected abstract Collection<String> getUserRoles(Object context, Set<String> mappableRoles);
/**
* @param aJ2eeMappableRolesRetriever
* The MappableAttributesRetriever to use
*/
public void setMappableRolesRetriever(MappableAttributesRetriever aJ2eeMappableRolesRetriever) {
this.j2eeMappableRoles = aJ2eeMappableRolesRetriever.getMappableAttributes();
}
/**
* @param mapper
* The Attributes2GrantedAuthoritiesMapper to use
*/
public void setUserRoles2GrantedAuthoritiesMapper(Attributes2GrantedAuthoritiesMapper mapper) {
j2eeUserRoles2GrantedAuthoritiesMapper = mapper;
}
}
@@ -1,49 +0,0 @@
package org.springframework.security.ui.preauth.j2ee;
import org.springframework.security.ui.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
/**
* Implementation of AuthenticationDetailsSource which converts the user's J2EE roles (as obtained by calling
* {@link HttpServletRequest#isUserInRole(String)}) into GrantedAuthoritys and stores these in the authentication
* details object (.
*
* @author Ruud Senden
* @since 2.0
*/
public class J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource extends AbstractPreAuthenticatedAuthenticationDetailsSource {
/**
* Public constructor which overrides the default AuthenticationDetails
* class to be used.
*/
public J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() {
super.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class);
j2eeUserRoles2GrantedAuthoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper();
}
/**
* Obtains the list of user roles based on the current user's J2EE roles.
*
* @param request The request against which <tt>isUserInRole</tt> will be called for each role name
* returned by the MappableAttributesRetriever.
* @return GrantedAuthority[] mapped from the user's J2EE roles.
*/
protected Collection<String> getUserRoles(Object context, Set<String> mappableRoles) {
ArrayList<String> j2eeUserRolesList = new ArrayList<String>();
for (String role : mappableRoles) {
if (((HttpServletRequest)context).isUserInRole(role)) {
j2eeUserRolesList.add(role);
}
}
return j2eeUserRolesList;
}
}
@@ -1,39 +0,0 @@
package org.springframework.security.ui.preauth.j2ee;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
/**
* This AbstractPreAuthenticatedProcessingFilter implementation is based on the
* J2EE container-based authentication mechanism. It will use the J2EE user
* principal name as the pre-authenticated principal.
*
* @author Ruud Senden
* @since 2.0
*/
public class J2eePreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
/**
* Return the J2EE user name.
*/
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) {
Object principal = httpRequest.getUserPrincipal() == null ? null : httpRequest.getUserPrincipal().getName();
if (logger.isDebugEnabled()) {
logger.debug("PreAuthenticated J2EE principal: " + principal);
}
return principal;
}
/**
* For J2EE container-based authentication there is no generic way to
* retrieve the credentials, as such this method returns a fixed dummy
* value.
*/
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpRequest) {
return "N/A";
}
public int getOrder() {
return 0;
}
}
@@ -1,50 +0,0 @@
package org.springframework.security.ui.preauth.j2ee;
import java.io.InputStream;
import org.springframework.security.authoritymapping.XmlMappableAttributesRetriever;
/**
* <p>
* This MappableAttributesRetriever implementation reads the list of defined J2EE
* roles from a web.xml file. It's functionality is based on the
* XmlMappableAttributesRetriever base class.
* <p>
* Example on how to configure this MappableAttributesRetriever in the Spring
* configuration file:
*
* <pre>
*
*
* &lt;bean id=&quot;j2eeMappableRolesRetriever&quot; class=&quot;org.springframework.security.ui.preauth.j2ee.WebXmlMappableAttributesRetriever&quot;&gt;
* &lt;property name=&quot;webXmlInputStream&quot;&gt;&lt;bean factory-bean=&quot;webXmlResource&quot; factory-method=&quot;getInputStream&quot;/&gt;&lt;/property&gt;
* &lt;/bean&gt;
* &lt;bean id=&quot;webXmlResource&quot; class=&quot;org.springframework.web.context.support.ServletContextResource&quot;&gt;
* &lt;constructor-arg&gt;&lt;ref local=&quot;servletContext&quot;/&gt;&lt;/constructor-arg&gt;
* &lt;constructor-arg&gt;&lt;value&gt;/WEB-INF/web.xml&lt;/value&gt;&lt;/constructor-arg&gt;
* &lt;/bean&gt;
* &lt;bean id=&quot;servletContext&quot; class=&quot;org.springframework.web.context.support.ServletContextFactoryBean&quot;/&gt;
*
* </pre>
*
* @author Ruud Senden
* @since 2.0
*/
public class WebXmlMappableAttributesRetriever extends XmlMappableAttributesRetriever {
private static final String XPATH_EXPR = "/web-app/security-role/role-name/text()";
/**
* Constructor setting the XPath expression to use
*/
public WebXmlMappableAttributesRetriever() {
super.setXpathExpression(XPATH_EXPR);
}
/**
* @param anInputStream
* The InputStream to read the XML data from
*/
public void setWebXmlInputStream(InputStream anInputStream) {
super.setXmlInputStream(anInputStream);
}
}
@@ -1,206 +0,0 @@
package org.springframework.security.ui.preauth.websphere;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.security.auth.Subject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* WebSphere Security helper class to allow retrieval of the current username and groups.
* <p>
* See Spring Security Jira SEC-477.
*
* @author Ruud Senden
* @author Stephane Manciot
* @since 2.0
*/
final class WASSecurityHelper {
private static final Log logger = LogFactory.getLog(WASSecurityHelper.class);
private static final String USER_REGISTRY = "UserRegistry";
private static Method getRunAsSubject = null;
private static Method getGroupsForUser = null;
private static Method getSecurityName = null;
// SEC-803
private static Class<?> wsCredentialClass = null;
/**
* Get the security name for the given subject.
*
* @param subject
* The subject for which to retrieve the security name
* @return String the security name for the given subject
*/
private static final String getSecurityName(final Subject subject) {
if (logger.isDebugEnabled()) {
logger.debug("Determining Websphere security name for subject " + subject);
}
String userSecurityName = null;
if (subject != null) {
// SEC-803
Object credential = subject.getPublicCredentials(getWSCredentialClass()).iterator().next();
if (credential != null) {
userSecurityName = (String)invokeMethod(getSecurityNameMethod(),credential,null);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Websphere security name is " + userSecurityName + " for subject " + subject);
}
return userSecurityName;
}
/**
* Get the current RunAs subject.
*
* @return Subject the current RunAs subject
*/
private static final Subject getRunAsSubject() {
logger.debug("Retrieving WebSphere RunAs subject");
// get Subject: WSSubject.getCallerSubject ();
return (Subject) invokeMethod(getRunAsSubjectMethod(), null, new Object[] {});
}
/**
* Get the WebSphere group names for the given subject.
*
* @param subject
* The subject for which to retrieve the WebSphere group names
* @return the WebSphere group names for the given subject
*/
private static final String[] getWebSphereGroups(final Subject subject) {
return getWebSphereGroups(getSecurityName(subject));
}
/**
* Get the WebSphere group names for the given security name.
*
* @param securityName
* The securityname for which to retrieve the WebSphere group names
* @return the WebSphere group names for the given security name
*/
@SuppressWarnings("unchecked")
private static final String[] getWebSphereGroups(final String securityName) {
Context ic = null;
try {
// TODO: Cache UserRegistry object
ic = new InitialContext();
Object objRef = ic.lookup(USER_REGISTRY);
Object userReg = PortableRemoteObject.narrow(objRef, Class.forName ("com.ibm.websphere.security.UserRegistry"));
if (logger.isDebugEnabled()) {
logger.debug("Determining WebSphere groups for user " + securityName + " using WebSphere UserRegistry " + userReg);
}
final Collection groups = (Collection) invokeMethod(getGroupsForUserMethod(), userReg, new Object[]{ securityName });
if (logger.isDebugEnabled()) {
logger.debug("Groups for user " + securityName + ": " + groups.toString());
}
String[] result = new String[groups.size()];
return (String[]) groups.toArray(result);
} catch (Exception e) {
logger.error("Exception occured while looking up groups for user", e);
throw new RuntimeException("Exception occured while looking up groups for user", e);
} finally {
try {
ic.close();
} catch (NamingException e) {
logger.debug("Exception occured while closing context", e);
}
}
}
/**
* @return
*/
public static final String[] getGroupsForCurrentUser() {
return getWebSphereGroups(getRunAsSubject());
}
public static final String getCurrentUserName() {
return getSecurityName(getRunAsSubject());
}
private static final Object invokeMethod(Method method, Object instance, Object[] args)
{
try {
return method.invoke(instance,args);
} catch (IllegalArgumentException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+ Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
} catch (IllegalAccessException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
} catch (InvocationTargetException e) {
logger.error("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
throw new RuntimeException("Error while invoking method "+method.getClass().getName()+"."+method.getName()+"("+Arrays.asList(args)+")",e);
}
}
private static final Method getMethod(String className, String methodName, String[] parameterTypeNames) {
try {
Class<?> c = Class.forName(className);
final int len = parameterTypeNames.length;
Class<?>[] parameterTypes = new Class[len];
for (int i = 0; i < len; i++) {
parameterTypes[i] = Class.forName(parameterTypeNames[i]);
}
return c.getDeclaredMethod(methodName, parameterTypes);
} catch (ClassNotFoundException e) {
logger.error("Required class"+className+" not found");
throw new RuntimeException("Required class"+className+" not found",e);
} catch (NoSuchMethodException e) {
logger.error("Required method "+methodName+" with parameter types ("+ Arrays.asList(parameterTypeNames) +") not found on class "+className);
throw new RuntimeException("Required class"+className+" not found",e);
}
}
private static final Method getRunAsSubjectMethod() {
if (getRunAsSubject == null) {
getRunAsSubject = getMethod("com.ibm.websphere.security.auth.WSSubject", "getRunAsSubject", new String[] {});
}
return getRunAsSubject;
}
private static final Method getGroupsForUserMethod() {
if (getGroupsForUser == null) {
getGroupsForUser = getMethod("com.ibm.websphere.security.UserRegistry", "getGroupsForUser", new String[] { "java.lang.String" });
}
return getGroupsForUser;
}
private static final Method getSecurityNameMethod() {
if (getSecurityName == null) {
getSecurityName = getMethod("com.ibm.websphere.security.cred.WSCredential", "getSecurityName", new String[] {});
}
return getSecurityName;
}
// SEC-803
private static final Class<?> getWSCredentialClass() {
if (wsCredentialClass == null) {
wsCredentialClass = getClass("com.ibm.websphere.security.cred.WSCredential");
}
return wsCredentialClass;
}
private static final Class<?> getClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
logger.error("Required class " + className + " not found");
throw new RuntimeException("Required class " + className + " not found",e);
}
}
}
@@ -1,96 +0,0 @@
package org.springframework.security.ui.preauth.websphere;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.util.Assert;
/**
* This method interceptor can be used in front of arbitrary Spring beans to make a Spring SecurityContext
* available to the bean, based on the current WebSphere credentials.
*
* @author Ruud Senden
* @since 1.0
*/
public class WebSphere2SpringSecurityPropagationInterceptor implements MethodInterceptor {
private static final Log LOG = LogFactory.getLog(WebSphere2SpringSecurityPropagationInterceptor.class);
private AuthenticationManager authenticationManager = null;
private AuthenticationDetailsSource authenticationDetailsSource = new WebSpherePreAuthenticatedAuthenticationDetailsSource();
/**
* Authenticate with Spring Security based on WebSphere credentials before proceeding with method
* invocation, and clean up the Spring Security Context after method invocation finishes.
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
try {
LOG.debug("Performing Spring Security authentication with WebSphere credentials");
authenticateSpringSecurityWithWASCredentials(this);
LOG.debug("Proceeding with method invocation");
return methodInvocation.proceed();
} finally {
LOG.debug("Clearing Spring Security security context");
clearSpringSecurityContext();
}
}
/**
* Retrieve the current WebSphere credentials and authenticate them with Spring Security
* using the pre-authenticated authentication provider.
* @param aContext The context to use for building the authentication details.
*/
private final void authenticateSpringSecurityWithWASCredentials(Object aContext)
{
Assert.notNull(authenticationManager);
Assert.notNull(authenticationDetailsSource);
String userName = WASSecurityHelper.getCurrentUserName();
if (LOG.isDebugEnabled()) { LOG.debug("Creating authentication request for user "+userName); }
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(userName,null);
authRequest.setDetails(authenticationDetailsSource.buildDetails(null));
if (LOG.isDebugEnabled()) { LOG.debug("Authentication request for user "+userName+": "+authRequest); }
Authentication authResponse = authenticationManager.authenticate(authRequest);
if (LOG.isDebugEnabled()) { LOG.debug("Authentication response for user "+userName+": "+authResponse); }
SecurityContextHolder.getContext().setAuthentication(authResponse);
}
/**
* Clear the Spring Security Context
*/
private final void clearSpringSecurityContext()
{
SecurityContextHolder.clearContext();
}
/**
* @return Returns the authenticationManager.
*/
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
/**
* @param authenticationManager The authenticationManager to set.
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* @return Returns the authenticationDetailsSource.
*/
public AuthenticationDetailsSource getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
/**
* @param authenticationDetailsSource The authenticationDetailsSource to set.
*/
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
}
}
@@ -1,86 +0,0 @@
package org.springframework.security.ui.preauth.websphere;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.MutableGrantedAuthoritiesContainer;
import org.springframework.security.authoritymapping.Attributes2GrantedAuthoritiesMapper;
import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper;
import org.springframework.security.ui.AuthenticationDetailsSourceImpl;
import org.springframework.security.ui.preauth.PreAuthenticatedGrantedAuthoritiesAuthenticationDetails;
import org.springframework.util.Assert;
/**
* This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer,
* will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere
* user, mapped using the configured Attributes2GrantedAuthoritiesMapper.
*
* By default, this class is configured to build instances of the
* PreAuthenticatedGrantedAuthoritiesAuthenticationDetails class.
*
* @author Ruud Senden
*/
public class WebSpherePreAuthenticatedAuthenticationDetailsSource extends AuthenticationDetailsSourceImpl implements InitializingBean {
private final Log logger = LogFactory.getLog(getClass());
private Attributes2GrantedAuthoritiesMapper webSphereGroups2GrantedAuthoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper();
/**
* Public constructor which overrides the default AuthenticationDetails
* class to be used.
*/
public WebSpherePreAuthenticatedAuthenticationDetailsSource() {
super.setClazz(PreAuthenticatedGrantedAuthoritiesAuthenticationDetails.class);
}
/**
* Check that all required properties have been set.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(webSphereGroups2GrantedAuthoritiesMapper, "WebSphere groups to granted authorities mapper not set");
}
/**
* Build the authentication details object. If the specified authentication
* details class implements the PreAuthenticatedGrantedAuthoritiesSetter, a
* list of pre-authenticated Granted Authorities will be set based on the
* WebSphere groups for the current user.
*
* @see org.springframework.security.ui.AuthenticationDetailsSource#buildDetails(Object)
*/
public Object buildDetails(Object context) {
Object result = super.buildDetails(context);
if (result instanceof MutableGrantedAuthoritiesContainer) {
((MutableGrantedAuthoritiesContainer) result)
.setGrantedAuthorities(getWebSphereGroupsBasedGrantedAuthorities());
}
return result;
}
/**
* Get a list of Granted Authorities based on the current user's WebSphere groups.
*
* @return GrantedAuthority[] mapped from the user's WebSphere groups.
*/
private List<GrantedAuthority> getWebSphereGroupsBasedGrantedAuthorities() {
List<String> webSphereGroups = Arrays.asList(WASSecurityHelper.getGroupsForCurrentUser());
List<GrantedAuthority> userGas = webSphereGroups2GrantedAuthoritiesMapper.getGrantedAuthorities(webSphereGroups);
if (logger.isDebugEnabled()) {
logger.debug("WebSphere groups: " + webSphereGroups + " mapped to Granted Authorities: " + userGas);
}
return userGas;
}
/**
* @param mapper
* The Attributes2GrantedAuthoritiesMapper to use
*/
public void setWebSphereGroups2GrantedAuthoritiesMapper(Attributes2GrantedAuthoritiesMapper mapper) {
webSphereGroups2GrantedAuthoritiesMapper = mapper;
}
}
@@ -1,39 +0,0 @@
package org.springframework.security.ui.preauth.websphere;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
/**
* This AbstractPreAuthenticatedProcessingFilter implementation is based on
* WebSphere authentication. It will use the WebSphere RunAs user principal name
* as the pre-authenticated principal.
*
* @author Ruud Senden
* @since 2.0
*/
public class WebSpherePreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
/**
* Return the WebSphere user name.
*/
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) {
Object principal = WASSecurityHelper.getCurrentUserName();
if (logger.isDebugEnabled()) {
logger.debug("PreAuthenticated WebSphere principal: " + principal);
}
return principal;
}
/**
* For J2EE container-based authentication there is no generic way to
* retrieve the credentials, as such this method returns a fixed dummy
* value.
*/
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpRequest) {
return "N/A";
}
public int getOrder() {
return 0;
}
}
@@ -1,24 +0,0 @@
package org.springframework.security.ui.preauth.websphere;
import org.springframework.security.ui.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
/**
* This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer,
* will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere
* user, mapped using the configured Attributes2GrantedAuthoritiesMapper.
*
* By default, this class is configured to build instances of the
* PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails class.
*
* @author Ruud Senden
*/
public class WebSpherePreAuthenticatedWebAuthenticationDetailsSource extends WebSpherePreAuthenticatedAuthenticationDetailsSource {
/**
* Public constructor which overrides the default AuthenticationDetails
* class to be used.
*/
public WebSpherePreAuthenticatedWebAuthenticationDetailsSource() {
super();
super.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class);
}
}
@@ -1,84 +0,0 @@
package org.springframework.security.ui.preauth.x509;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.util.Assert;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.MessageSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* Obtains the principal from a certificate using a regular expression match against the Subject (as returned by a call
* to {@link X509Certificate#getSubjectDN()}).
* <p>
* The regular expression should contain a single group; for example the default expression "CN=(.?)," matches the
* common name field. So "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
* <p>
* The matches are case insensitive. So "emailAddress=(.?)," will match "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a
* user name "jimi@hendrix.org"
*
* @author Luke Taylor
* @version $Id$
*/
public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor {
//~ Instance fields ================================================================================================
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private Pattern subjectDnPattern;
public SubjectDnX509PrincipalExtractor() {
setSubjectDnRegex("CN=(.*?),");
}
public Object extractPrincipal(X509Certificate clientCert) {
//String subjectDN = clientCert.getSubjectX500Principal().getName();
String subjectDN = clientCert.getSubjectDN().getName();
logger.debug("Subject DN is '" + subjectDN + "'");
Matcher matcher = subjectDnPattern.matcher(subjectDN);
if (!matcher.find()) {
throw new BadCredentialsException(messages.getMessage("DaoX509AuthoritiesPopulator.noMatching",
new Object[] {subjectDN}, "No matching pattern was found in subject DN: {0}"));
}
if (matcher.groupCount() != 1) {
throw new IllegalArgumentException("Regular expression must contain a single group ");
}
String username = matcher.group(1);
logger.debug("Extracted Principal name is '" + username + "'");
return username;
}
/**
* Sets the regular expression which will by used to extract the user name from the certificate's Subject
* DN.
* <p>
* It should contain a single group; for example the default expression "CN=(.?)," matches the common
* name field. So "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
* <p>
* The matches are case insensitive. So "emailAddress=(.?)," will match "EMAILADDRESS=jimi@hendrix.org,
* CN=..." giving a user name "jimi@hendrix.org"
*
* @param subjectDnRegex the regular expression to find in the subject
*/
public void setSubjectDnRegex(String subjectDnRegex) {
Assert.hasText(subjectDnRegex, "Regular expression may not be null or empty");
subjectDnPattern = Pattern.compile(subjectDnRegex, Pattern.CASE_INSENSITIVE);
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}
@@ -1,55 +0,0 @@
package org.springframework.security.ui.preauth.x509;
import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.ui.FilterChainOrder;
import javax.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate;
/**
* @author Luke Taylor
* @version $Id$
*/
public class X509PreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
X509Certificate cert = extractClientCertificate(request);
if (cert == null) {
return null;
}
return principalExtractor.extractPrincipal(cert);
}
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return extractClientCertificate(request);
}
private X509Certificate extractClientCertificate(HttpServletRequest request) {
X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
if (logger.isDebugEnabled()) {
logger.debug("X.509 client authentication certificate:" + certs[0]);
}
return certs[0];
}
if (logger.isDebugEnabled()) {
logger.debug("No client certificate found in request.");
}
return null;
}
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
this.principalExtractor = principalExtractor;
}
public int getOrder() {
return FilterChainOrder.X509_FILTER;
}
}
@@ -1,17 +0,0 @@
package org.springframework.security.ui.preauth.x509;
import java.security.cert.X509Certificate;
/**
* Obtains the principal from an X509Certificate for use within the framework.
*
* @author Luke Taylor
* @version $Id$
*/
public interface X509PrincipalExtractor {
/**
* Returns the principal (usually a String) for the given certificate.
*/
Object extractPrincipal(X509Certificate cert);
}
@@ -1,384 +0,0 @@
package org.springframework.security.ui.rememberme;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.Authentication;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.AccountStatusException;
import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.WebAuthenticationDetailsSource;
import org.springframework.security.ui.logout.LogoutHandler;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.UserDetailsChecker;
import org.springframework.security.userdetails.checker.AccountStatusUserDetailsChecker;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Base class for RememberMeServices implementations.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
//~ Static fields/initializers =====================================================================================
public static final String SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
public static final String DEFAULT_PARAMETER = "_spring_security_remember_me";
public static final int TWO_WEEKS_S = 1209600;
private static final String DELIMITER = ":";
//~ Instance fields ================================================================================================
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private String cookieName = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY;
private String parameter = DEFAULT_PARAMETER;
private boolean alwaysRemember;
private String key;
private int tokenValiditySeconds = TWO_WEEKS_S;
public void afterPropertiesSet() throws Exception {
Assert.hasLength(key);
Assert.hasLength(parameter);
Assert.hasLength(cookieName);
Assert.notNull(userDetailsService);
}
/**
* Template implementation which locates the Spring Security cookie, decodes it into
* a delimited array of tokens and submits it to subclasses for processing
* via the <tt>processAutoLoginCookie</tt> method.
* <p>
* The returned username is then used to load the UserDetails object for the user, which in turn
* is used to create a valid authentication token.
*/
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
String rememberMeCookie = extractRememberMeCookie(request);
if (rememberMeCookie == null) {
return null;
}
logger.debug("Remember-me cookie detected");
UserDetails user = null;
try {
String[] cookieTokens = decodeCookie(rememberMeCookie);
user = processAutoLoginCookie(cookieTokens, request, response);
userDetailsChecker.check(user);
} catch (CookieTheftException cte) {
cancelCookie(request, response);
throw cte;
} catch (UsernameNotFoundException noUser) {
cancelCookie(request, response);
logger.debug("Remember-me login was valid but corresponding user not found.", noUser);
return null;
} catch (InvalidCookieException invalidCookie) {
cancelCookie(request, response);
logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
return null;
} catch (AccountStatusException statusInvalid) {
cancelCookie(request, response);
logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
return null;
} catch (RememberMeAuthenticationException e) {
cancelCookie(request, response);
logger.debug(e.getMessage());
return null;
}
logger.debug("Remember-me cookie accepted");
return createSuccessfulAuthentication(request, user);
}
/**
* Locates the Spring Security remember me cookie in the request and returns its value.
*
* @param request the submitted request which is to be authenticated
* @return the cookie value (if present), null otherwise.
*/
protected String extractRememberMeCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if ((cookies == null) || (cookies.length == 0)) {
return null;
}
for (int i = 0; i < cookies.length; i++) {
if (cookieName.equals(cookies[i].getName())) {
return cookies[i].getValue();
}
}
return null;
}
/**
* Creates the final <tt>Authentication</tt> object returned from the <tt>autoLogin</tt> method.
* <p>
* By default it will create a <tt>RememberMeAuthenticationToken</tt> instance.
*
* @param request the original request. The configured <tt>AuthenticationDetailsSource</tt> will
* use this to build the details property of the returned object.
* @param user the <tt>UserDetails</tt> loaded from the <tt>UserDetailsService</tt>. This will be
* stored as the principal.
*
* @return the <tt>Authentication</tt> for the remember-me authenticated user
*/
protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {
RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, user, user.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
/**
* Decodes the cookie and splits it into a set of token strings using the ":" delimiter.
*
* @param cookieValue the value obtained from the submitted cookie
* @return the array of tokens.
* @throws InvalidCookieException if the cookie was not base64 encoded.
*/
protected String[] decodeCookie(String cookieValue) throws InvalidCookieException {
for (int j = 0; j < cookieValue.length() % 4; j++) {
cookieValue = cookieValue + "=";
}
if (!Base64.isArrayByteBase64(cookieValue.getBytes())) {
throw new InvalidCookieException( "Cookie token was not Base64 encoded; value was '" + cookieValue + "'");
}
String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
return StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER);
}
/**
* Inverse operation of decodeCookie.
*
* @param cookieTokens the tokens to be encoded.
* @return base64 encoding of the tokens concatenated with the ":" delimiter.
*/
protected String encodeCookie(String[] cookieTokens) {
StringBuffer sb = new StringBuffer();
for(int i=0; i < cookieTokens.length; i++) {
sb.append(cookieTokens[i]);
if (i < cookieTokens.length - 1) {
sb.append(DELIMITER);
}
}
String value = sb.toString();
sb = new StringBuffer(new String(Base64.encodeBase64(value.getBytes())));
while (sb.charAt(sb.length() - 1) == '=') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
logger.debug("Interactive login attempt was unsuccessful.");
cancelCookie(request, response);
onLoginFail(request, response);
}
protected void onLoginFail(HttpServletRequest request, HttpServletResponse response) {}
/**
* Examines the incoming request and checks for the presence of the configured "remember me" parameter.
* If it's present, or if <tt>alwaysRemember</tt> is set to true, calls <tt>onLoginSucces</tt>.
*/
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
if (!rememberMeRequested(request, parameter)) {
logger.debug("Remember-me login not requested.");
return;
}
onLoginSuccess(request, response, successfulAuthentication);
}
/**
* Called from loginSuccess when a remember-me login has been requested.
* Typically implemented by subclasses to set a remember-me cookie and potentially store a record
* of it if the implementation requires this.
*/
protected abstract void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
/**
* Allows customization of whether a remember-me login has been requested.
* The default is to return true if <tt>alwaysRemember</tt> is set or the configured parameter name has
* been included in the request and is set to the value "true".
*
* @param request the request submitted from an interactive login, which may include additional information
* indicating that a persistent login is desired.
* @param parameter the configured remember-me parameter name.
*
* @return true if the request includes information indicating that a persistent login has been
* requested.
*/
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (alwaysRemember) {
return true;
}
String paramValue = request.getParameter(parameter);
if (paramValue != null) {
if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") ||
paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
return true;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
}
return false;
}
/**
* Called from autoLogin to process the submitted persistent login cookie. Subclasses should
* validate the cookie and perform any additional management required.
*
* @param cookieTokens the decoded and tokenized cookie value
* @param request the request
* @param response the response, to allow the cookie to be modified if required.
* @return the UserDetails for the corresponding user account if the cookie was validated successfully.
* @throws RememberMeAuthenticationException if the cookie is invalid or the login is invalid for some
* other reason.
* @throws UsernameNotFoundException if the user account corresponding to the login cookie couldn't be found
* (for example if the user has been removed from the system).
*/
protected abstract UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
HttpServletResponse response) throws RememberMeAuthenticationException, UsernameNotFoundException;
/**
* Sets a "cancel cookie" (with maxAge = 0) on the response to disable persistent logins.
*
* @param request
* @param response
*/
protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {
logger.debug("Cancelling cookie");
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
}
/**
* Sets the cookie on the response
*
* @param tokens the tokens which will be encoded to make the cookie value.
* @param maxAge the value passed to {@link Cookie#setMaxAge(int)}
* @param request the request
* @param response the response to add the cookie to.
*/
protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {
String cookieValue = encodeCookie(tokens);
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(maxAge);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
}
/**
* Implementation of <tt>LogoutHandler</tt>. Default behaviour is to call <tt>cancelCookie()</tt>.
*/
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
if (logger.isDebugEnabled()) {
logger.debug( "Logout of user "
+ (authentication == null ? "Unknown" : authentication.getName()));
}
cancelCookie(request, response);
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}
protected String getCookieName() {
return cookieName;
}
public void setAlwaysRemember(boolean alwaysRemember) {
this.alwaysRemember = alwaysRemember;
}
/**
* Sets the name of the parameter which should be checked for to see if a remember-me has been requested
* during a login request. This should be the same name you assign to the checkbox in your login form.
*
* @param parameter the HTTP request parameter
*/
public void setParameter(String parameter) {
Assert.hasText(parameter, "Parameter name cannot be null");
this.parameter = parameter;
}
public String getParameter() {
return parameter;
}
protected UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
Assert.notNull(userDetailsService, "UserDetailsService canot be null");
this.userDetailsService = userDetailsService;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setTokenValiditySeconds(int tokenValiditySeconds) {
this.tokenValiditySeconds = tokenValiditySeconds;
}
protected int getTokenValiditySeconds() {
return tokenValiditySeconds;
}
protected AuthenticationDetailsSource getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource cannot be null");
this.authenticationDetailsSource = authenticationDetailsSource;
}
}

Some files were not shown because too many files have changed in this diff Show More