diff --git a/.gitignore b/.gitignore index 120746776..cb4a7bc89 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ stacktrace.log /grails-spring-security-core-*.zip /testapps.config.groovy /plugin.xml +.settings \ No newline at end of file diff --git a/application.properties b/application.properties index de5c4a561..e151936c6 100644 --- a/application.properties +++ b/application.properties @@ -1,4 +1 @@ -#Grails Metadata file -#Tue Jul 17 11:29:03 EDT 2012 app.grails.version=2.0.4 -plugins.hibernate=2.0.4 diff --git a/scripts/S2CreatePersistentToken.groovy b/scripts/S2CreatePersistentToken.groovy index 8b4176c63..c6766158b 100644 --- a/scripts/S2CreatePersistentToken.groovy +++ b/scripts/S2CreatePersistentToken.groovy @@ -13,7 +13,7 @@ * limitations under the License. */ -includeTargets << new File("$springSecurityCorePluginDir/scripts/_S2Common.groovy") +includeTargets << new File(springSecurityCorePluginDir, 'scripts/_S2Common.groovy') fullClassName = null @@ -61,16 +61,19 @@ private boolean configure() { private void createDomainClass() { String dir = packageToDir(templateAttributes.packageName) generateFile "$templateDir/PersistentLogin.groovy.template", - "$appDir/domain/${dir}${templateAttributes.className}.groovy" + "$appDir/domain/${dir}${templateAttributes.className}.groovy" } private void updateConfig() { def configFile = new File(appDir, 'conf/Config.groovy') - if (configFile.exists()) { - configFile.withWriterAppend { - it.writeLine "grails.plugins.springsecurity.rememberMe.persistent = true" - it.writeLine "grails.plugins.springsecurity.rememberMe.persistentToken.domainClassName = '$fullClassName'" - } + if (!configFile.exists()) { + return + } + + configFile.withWriterAppend { BufferedWriter writer -> + writer.writeLine "grails.plugin.springsecurity.rememberMe.persistent = true" + writer.writeLine "grails.plugin.springsecurity.rememberMe.persistentToken.domainClassName = '$fullClassName'" + writer.newLine() } } diff --git a/scripts/_S2Common.groovy b/scripts/_S2Common.groovy index 3e1ff533c..3163b7b94 100644 --- a/scripts/_S2Common.groovy +++ b/scripts/_S2Common.groovy @@ -33,7 +33,7 @@ packageToDir = { String packageName -> okToWrite = { String dest -> - def file = new File(dest) + File file = new File(dest) if (overwriteAll || !file.exists()) { return true } diff --git a/src/groovy/grails/plugin/springsecurity/web/authentication/rememberme/GormPersistentTokenRepository.groovy b/src/groovy/grails/plugin/springsecurity/web/authentication/rememberme/GormPersistentTokenRepository.groovy index 7585c306c..531893605 100644 --- a/src/groovy/grails/plugin/springsecurity/web/authentication/rememberme/GormPersistentTokenRepository.groovy +++ b/src/groovy/grails/plugin/springsecurity/web/authentication/rememberme/GormPersistentTokenRepository.groovy @@ -29,7 +29,7 @@ import org.springframework.security.web.authentication.rememberme.PersistentToke */ class GormPersistentTokenRepository implements PersistentTokenRepository { - private final Logger log = LoggerFactory.getLogger(getClass()) + protected final Logger log = LoggerFactory.getLogger(getClass()) /** Dependency injection for grailsApplication */ GrailsApplication grailsApplication @@ -40,13 +40,13 @@ class GormPersistentTokenRepository implements PersistentTokenRepository { * org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken) */ void createNewToken(PersistentRememberMeToken token) { - // join an existing transaction if one is active def clazz = lookupDomainClass() if (!clazz) return + // join an existing transaction if one is active clazz.withTransaction { status -> clazz.newInstance(username: token.username, series: token.series, - token: token.tokenValue, lastUsed: token.date).save() + token: token.tokenValue, lastUsed: token.date).save() } } diff --git a/src/java/grails/plugin/springsecurity/SecurityEventListener.java b/src/java/grails/plugin/springsecurity/SecurityEventListener.java index 96c812487..b3f1878e3 100644 --- a/src/java/grails/plugin/springsecurity/SecurityEventListener.java +++ b/src/java/grails/plugin/springsecurity/SecurityEventListener.java @@ -42,7 +42,7 @@ * All callbacks are optional; you can implement just the ones you're interested in, e.g. *
  * grails {
- *    plugins {
+ *    plugin {
  *       springsecurity {
  *          ...
  *          onAuthenticationSuccessEvent = { e, appCtx ->
@@ -59,7 +59,7 @@
  */
 public class SecurityEventListener implements ApplicationListener, ApplicationContextAware {
 
-	private ApplicationContext _applicationContext;
+	protected ApplicationContext applicationContext;
 
 	/**
 	 * {@inheritDoc}
@@ -89,10 +89,10 @@ else if (e instanceof AbstractAuthorizationEvent) {
 	}
 
 	@SuppressWarnings("rawtypes")
-	private void call(final ApplicationEvent e, final String closureName) {
+	protected void call(final ApplicationEvent e, final String closureName) {
 		Object closure = SpringSecurityUtils.getSecurityConfig().get(closureName);
 		if (closure instanceof Closure) {
-			((Closure)closure).call(new Object[] { e, _applicationContext });
+			((Closure)closure).call(new Object[] { e, applicationContext });
 		}
 	}
 
@@ -101,7 +101,7 @@ private void call(final ApplicationEvent e, final String closureName) {
  	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(
  	 * 	org.springframework.context.ApplicationContext)
  	 */
- 	public void setApplicationContext(final ApplicationContext applicationContext) {
- 		_applicationContext = applicationContext;
+ 	public void setApplicationContext(final ApplicationContext ctx) {
+ 		applicationContext = ctx;
  	}
 }
diff --git a/src/java/grails/plugin/springsecurity/SecurityFilterPosition.java b/src/java/grails/plugin/springsecurity/SecurityFilterPosition.java
index 611748ba9..d7866bf4d 100644
--- a/src/java/grails/plugin/springsecurity/SecurityFilterPosition.java
+++ b/src/java/grails/plugin/springsecurity/SecurityFilterPosition.java
@@ -69,14 +69,15 @@ public enum SecurityFilterPosition {
 	LAST(Integer.MAX_VALUE);
 
 	private static final int INTERVAL = 100;
-	private final int _order;
+
+	private final int order;
 
 	private SecurityFilterPosition() {
-		_order = ordinal() * INTERVAL;
+		order = ordinal() * INTERVAL;
 	}
 
-	private SecurityFilterPosition(final int order) {
-		_order = order;
+	private SecurityFilterPosition(final int filterOrder) {
+		order = filterOrder;
 	}
 
 	/**
@@ -84,6 +85,6 @@ private SecurityFilterPosition(final int order) {
 	 * @return the order
 	 */
 	public int getOrder() {
-		return _order;
+		return order;
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java b/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java
index 5a27bf716..a816e641d 100644
--- a/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java
+++ b/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java
@@ -21,9 +21,6 @@
 import groovy.util.ConfigObject;
 import groovy.util.ConfigSlurper;
 
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -40,12 +37,13 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
+import org.apache.commons.lang.StringEscapeUtils;
 import org.codehaus.groovy.grails.commons.GrailsApplication;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserCache;
@@ -57,6 +55,8 @@
 import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.savedrequest.SavedRequest;
+import org.springframework.security.web.util.AnyRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
 import org.springframework.util.StringUtils;
 
 /**
@@ -67,63 +67,27 @@
 public final class SpringSecurityUtils {
 
 	private static ConfigObject _securityConfig;
-	private static GrailsApplication _application;
-	private static final Map _context = new HashMap();
-	private static final String VOTER_NAMES_KEY = "VOTER_NAMES";
-	private static final String PROVIDER_NAMES_KEY = "PROVIDER_NAMES";
-	private static final String LOGOUT_HANDLER_NAMES_KEY = "LOGOUT_HANDLER_NAMES";
-	private static final String ORDERED_FILTERS_KEY = "ORDERED_FILTERS";
-	private static final String CONFIGURED_ORDERED_FILTERS_KEY = "CONFIGURED_ORDERED_FILTERS";
+	private static GrailsApplication application;
 
-	/**
-	 * Default value for the name of the Ajax header.
-	 */
-	public static final String AJAX_HEADER = "X-Requested-With";
+	private static List providerNames = new ArrayList();
+	private static List logoutHandlerNames = new ArrayList();
+	private static List voterNames = new ArrayList();
+	private static Map orderedFilters = new HashMap();
+	private static SortedMap configuredOrderedFilters = new TreeMap();
 
-	/**
-	 * @deprecated use {@link #getOrderedFilters()}
-	 */
-	@SuppressWarnings("unchecked")
-	@Deprecated
-	public static final Map ORDERED_FILTERS =
-		(Map)createDelegate(
-			ORDERED_FILTERS_KEY, Map.class, HashMap.class);
+	// HttpSessionRequestCache.SAVED_REQUEST is package-scope
+	public static final String SAVED_REQUEST = "SPRING_SECURITY_SAVED_REQUEST"; // TODO use requestCache
 
-	/**
-	 * @deprecated use {@link #getConfiguredOrderedFilters()}
-	 */
-	@SuppressWarnings("unchecked")
-	@Deprecated
-	public static final SortedMap CONFIGURED_ORDERED_FILTERS =
-		(SortedMap)createDelegate(
-				CONFIGURED_ORDERED_FILTERS_KEY, SortedMap.class, TreeMap.class);
+	// UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY is deprecated
+   public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";
 
-	/**
-	 * @deprecated use {@link #getVoterNames()}
-	 */
-	@SuppressWarnings("unchecked")
-	@Deprecated
-	public static final List VOTER_NAMES =
-		(List)createDelegate(
-			VOTER_NAMES_KEY, List.class, ArrayList.class);
-
-	/**
-	 * @deprecated use {@link #getProviderNames()}
-	 */
-	@SuppressWarnings("unchecked")
-	@Deprecated
-	public static final List PROVIDER_NAMES =
-		(List)createDelegate(
-				PROVIDER_NAMES_KEY, List.class, ArrayList.class);
+   // AbstractAuthenticationTargetUrlRequestHandler.DEFAULT_TARGET_PARAMETER was removed
+   public static final String DEFAULT_TARGET_PARAMETER = "spring-security-redirect";
 
 	/**
-	 * @deprecated use {@link #getLogoutHandlerNames()}
+	 * Default value for the name of the Ajax header.
 	 */
-	@SuppressWarnings("unchecked")
-	@Deprecated
-	public static final List LOGOUT_HANDLER_NAMES =
-		(List)createDelegate(
-				LOGOUT_HANDLER_NAMES_KEY, List.class, ArrayList.class);
+	public static final String AJAX_HEADER = "X-Requested-With";
 
 	/**
 	 * Used to ensure that all authenticated users have at least one granted authority to work
@@ -138,10 +102,10 @@ private SpringSecurityUtils() {
 
 	/**
 	 * Set at startup by plugin.
-	 * @param application the application
+	 * @param app the application
 	 */
-	public static void setApplication(GrailsApplication application) {
-		_application = application;
+	public static void setApplication(GrailsApplication app) {
+		application = app;
 		initializeContext();
 	}
 
@@ -156,8 +120,8 @@ public static Set authoritiesToRoles(final Object authorities) {
 			String authorityName = ((GrantedAuthority)authority).getAuthority();
 			if (null == authorityName) {
 				throw new IllegalArgumentException(
-						"Cannot process GrantedAuthority objects which return null from getAuthority() - attempting to process "
-						+ authority);
+						"Cannot process GrantedAuthority objects which return null " +
+						"from getAuthority() - attempting to process " + authority);
 			}
 			roles.add(authorityName);
 		}
@@ -175,7 +139,7 @@ public static Collection getPrincipalAuthorities() {
 			return Collections.emptyList();
 		}
 
-		Collection authorities = authentication.getAuthorities();
+		Collection authorities = authentication.getAuthorities();
 		if (authorities == null) {
 			return Collections.emptyList();
 		}
@@ -201,7 +165,7 @@ public static List parseAuthoritiesString(final String roleNam
 		for (String auth : StringUtils.commaDelimitedListToStringArray(roleNames)) {
 			auth = auth.trim();
 			if (auth.length() > 0) {
-				requiredAuthorities.add(new GrantedAuthorityImpl(auth));
+				requiredAuthorities.add(new SimpleGrantedAuthority(auth));
 			}
 		}
 
@@ -227,7 +191,7 @@ public static Set retainAll(final Object granted, final Object required)
 	 * @return true if the user is authenticated and has all the roles
 	 */
 	public static boolean ifAllGranted(final String roles) {
-		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
+		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
 		return inferred.containsAll(parseAuthoritiesString(roles));
 	}
 
@@ -237,7 +201,7 @@ public static boolean ifAllGranted(final String roles) {
 	 * @return true if the user is authenticated and has none the roles
 	 */
 	public static boolean ifNotGranted(final String roles) {
-		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
+		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
 		Set grantedCopy = retainAll(inferred, parseAuthoritiesString(roles));
 		return grantedCopy.isEmpty();
 	}
@@ -248,7 +212,7 @@ public static boolean ifNotGranted(final String roles) {
 	 * @return true if the user is authenticated and has any the roles
 	 */
 	public static boolean ifAnyGranted(final String roles) {
-		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
+		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
 		Set grantedCopy = retainAll(inferred, parseAuthoritiesString(roles));
 		return !grantedCopy.isEmpty();
 	}
@@ -315,9 +279,12 @@ public static boolean isAjax(final HttpServletRequest request) {
 		}
 
 		// check the SavedRequest's headers
-		SavedRequest savedRequest = (SavedRequest)request.getSession().getAttribute(WebAttributes.SAVED_REQUEST);
-		if (savedRequest != null) {
-			return !savedRequest.getHeaderValues(ajaxHeaderName).isEmpty();
+		HttpSession httpSession = SecurityRequestHolder.getRequest().getSession(false);
+		if (httpSession != null) {
+			SavedRequest savedRequest = (SavedRequest)httpSession.getAttribute(SAVED_REQUEST);
+			if (savedRequest != null) {
+				return !savedRequest.getHeaderValues(ajaxHeaderName).isEmpty();
+			}
 		}
 
 		return false;
@@ -331,16 +298,15 @@ public static boolean isAjax(final HttpServletRequest request) {
 	 * @param beanName the Spring bean name of the provider
 	 */
 	public static void registerProvider(final String beanName) {
-		getProviderNames().add(0, beanName);
+		providerNames.add(0, beanName);
 	}
 
 	/**
 	 * Authentication provider names. Plugins add or remove them, and can be overridden by config.
 	 * @return the names
 	 */
-	@SuppressWarnings("unchecked")
-	public static synchronized List getProviderNames() {
-		return (List)getFromContext(PROVIDER_NAMES_KEY);
+	public static List getProviderNames() {
+		return providerNames;
 	}
 
 	/**
@@ -351,16 +317,15 @@ public static synchronized List getProviderNames() {
 	 * @param beanName the Spring bean name of the handler
 	 */
 	public static void registerLogoutHandler(final String beanName) {
-		getLogoutHandlerNames().add(0, beanName);
+		logoutHandlerNames.add(0, beanName);
 	}
 
 	/**
 	 * Logout handler names. Plugins add or remove them, and can be overridden by config.
 	 * @return the names
 	 */
-	@SuppressWarnings("unchecked")
-	public static synchronized List getLogoutHandlerNames() {
-		return (List)getFromContext(LOGOUT_HANDLER_NAMES_KEY);
+	public static List getLogoutHandlerNames() {
+		return logoutHandlerNames;
 	}
 
 	/**
@@ -371,16 +336,15 @@ public static synchronized List getLogoutHandlerNames() {
 	 * @param beanName the Spring bean name of the voter
 	 */
 	public static void registerVoter(final String beanName) {
-		getVoterNames().add(0, beanName);
+		voterNames.add(0, beanName);
 	}
 
 	/**
 	 * Voter names. Plugins add or remove them and can be overridden by config.
 	 * @return the names
 	 */
-	@SuppressWarnings("unchecked")
 	public static List getVoterNames() {
-		return (List)getFromContext(VOTER_NAMES_KEY);
+		return voterNames;
 	}
 
 	/**
@@ -419,9 +383,8 @@ public static void registerFilter(final String beanName, final int order) {
 	 * Ordered filter names. Plugins add or remove them, and can be overridden by config.
 	 * @return the names
 	 */
-	@SuppressWarnings("unchecked")
 	public static Map getOrderedFilters() {
-		return (Map)getFromContext(ORDERED_FILTERS_KEY);
+		return orderedFilters;
 	}
 
 	/**
@@ -448,6 +411,7 @@ public static void clientRegisterFilter(final String beanName, final SecurityFil
 	 * @param beanName the Spring bean name of the filter
 	 * @param order the position (see {@link SecurityFilterPosition})
 	 */
+	@SuppressWarnings("deprecation")
 	public static void clientRegisterFilter(final String beanName, final int order) {
 
 		Filter oldFilter = getConfiguredOrderedFilters().get(order);
@@ -460,18 +424,17 @@ public static void clientRegisterFilter(final String beanName, final int order)
 		Filter filter = getBean(beanName);
 		getConfiguredOrderedFilters().put(order, filter);
 		FilterChainProxy filterChain = getBean("springSecurityFilterChain");
-		filterChain.setFilterChainMap(Collections.singletonMap(
-				filterChain.getMatcher().getUniversalMatchPattern(),
-				new ArrayList(getConfiguredOrderedFilters().values())));
+		RequestMatcher rm = new AnyRequestMatcher();
+		List filters = new ArrayList(getConfiguredOrderedFilters().values());
+		filterChain.setFilterChainMap(Collections.singletonMap(rm, filters));
 	}
 
 	/**
 	 * Set by SpringSecurityCoreGrailsPlugin; contains the actual filter beans in order.
 	 * @return the filters
 	 */
-	@SuppressWarnings("unchecked")
 	public static SortedMap getConfiguredOrderedFilters() {
-		return (SortedMap)getFromContext(CONFIGURED_ORDERED_FILTERS_KEY);
+		return configuredOrderedFilters;
 	}
 
 	/**
@@ -479,7 +442,16 @@ public static SortedMap getConfiguredOrderedFilters() {
 	 * @return true if logged in and switched
 	 */
 	public static boolean isSwitched() {
-		return ifAllGranted(SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR);
+		Collection inferred = findInferredAuthorities(getPrincipalAuthorities());
+		for (GrantedAuthority authority : inferred) {
+			if (authority instanceof SwitchUserGrantedAuthority) {
+				return true;
+			}
+			if (SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR.equals(authority.getAuthority())) {
+				return true;
+			}
+		}
+		return false;
 	}
 
 	/**
@@ -537,11 +509,12 @@ public static Object doWithAuth(@SuppressWarnings("rawtypes") final Closure clos
 		boolean set = false;
 		if (SecurityContextHolder.getContext().getAuthentication() == null) {
 			HttpSession httpSession = SecurityRequestHolder.getRequest().getSession(false);
-			SecurityContext context = null;
+			SecurityContext securityContext = null;
 			if (httpSession != null) {
-				context = (SecurityContext)httpSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
-				if (context != null) {
-					SecurityContextHolder.setContext(context);
+				securityContext = (SecurityContext)httpSession.getAttribute(
+						HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
+				if (securityContext != null) {
+					SecurityContextHolder.setContext(securityContext);
 					set = true;
 				}
 			}
@@ -584,6 +557,45 @@ public static Object doWithAuth(final String username, @SuppressWarnings("rawtyp
 		}
 	}
 
+	public static SecurityContext getSecurityContext(final HttpSession session) {
+		Object securityContext = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
+		if (securityContext instanceof SecurityContext) {
+			return (SecurityContext)securityContext;
+		}
+		return null;
+	}
+
+	/**
+	 * Get the last auth exception.
+	 * @param session the session
+	 * @return the exception
+	 */
+	public static Throwable getLastException(final HttpSession session) {
+		return (Throwable)session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
+	}
+
+	/**
+	 * Get the last attempted username.
+	 * @param session the session
+	 * @return the username
+	 */
+	public static String getLastUsername(final HttpSession session) {
+		String username = (String)session.getAttribute(SPRING_SECURITY_LAST_USERNAME_KEY);
+		if (username != null) {
+			username = StringEscapeUtils.unescapeHtml(username);
+		}
+		return username;
+	}
+
+	/**
+	 * Get the saved request from the session.
+	 * @param session the session
+	 * @return the saved request
+	 */
+	public static SavedRequest getSavedRequest(final HttpSession session) {
+		return (SavedRequest)session.getAttribute(SAVED_REQUEST);
+	}
+
 	/**
 	 * Merge in a secondary config (provided by a plugin as defaults) into the main config.
 	 * @param currentConfig the current configuration
@@ -597,7 +609,6 @@ private static void mergeConfig(final ConfigObject currentConfig, final String c
 			secondaryConfig = slurper.parse(classLoader.loadClass(className));
 		}
 		catch (ClassNotFoundException e) {
-			// TODO fix this
 			throw new RuntimeException(e);
 		}
 
@@ -626,10 +637,10 @@ private static ConfigObject mergeConfig(final ConfigObject currentConfig, final
 		return config;
 	}
 
-	private static Collection findInferredAuthorities(
+	private static Collection findInferredAuthorities(
 			final Collection granted) {
 		RoleHierarchy roleHierarchy = getBean("roleHierarchy");
-		Collection reachable = roleHierarchy.getReachableGrantedAuthorities(granted);
+		Collection reachable = roleHierarchy.getReachableGrantedAuthorities(granted);
 		if (reachable == null) {
 			return Collections.emptyList();
 		}
@@ -638,28 +649,7 @@ private static Collection findInferredAuthorities(
 
 	@SuppressWarnings("unchecked")
 	private static  T getBean(final String name) {
-		return (T)_application.getMainContext().getBean(name);
-	}
-
-	private static Object createDelegate(final String configKey,
-			Class interfaceClass, Class implClass) {
-
-		try {
-			storeInContext(configKey, implClass.newInstance());
-		}
-		catch (InstantiationException impossible) {
-			// impossible with regular java.util classes
-		}
-		catch (IllegalAccessException impossible) {
-			// impossible with regular java.util classes
-		}
-
-		return Proxy.newProxyInstance(implClass.getClassLoader(),
-				new Class[] { interfaceClass }, new InvocationHandler() {
-			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-				return method.invoke(getFromContext(configKey), args);
-			}
-		});
+		return (T)application.getMainContext().getBean(name);
 	}
 
 	/**
@@ -667,30 +657,23 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
 	 * to default values when running integration and functional tests together.
 	 */
 	private static void initializeContext() {
-		getVoterNames().clear();
-		getVoterNames().add("authenticatedVoter");
-		getVoterNames().add("roleVoter");
-		getVoterNames().add("webExpressionVoter");
-
-		getLogoutHandlerNames().clear();
-		getLogoutHandlerNames().add("rememberMeServices");
-		getLogoutHandlerNames().add("securityContextLogoutHandler");
+		voterNames.clear();
+		voterNames.add("authenticatedVoter");
+		voterNames.add("roleVoter");
+		voterNames.add("webExpressionVoter");
+		voterNames.add("closureVoter");
 
-		getProviderNames().clear();
-		getProviderNames().add("daoAuthenticationProvider");
-		getProviderNames().add("anonymousAuthenticationProvider");
-		getProviderNames().add("rememberMeAuthenticationProvider");
+		logoutHandlerNames.clear();
+		logoutHandlerNames.add("rememberMeServices");
+		logoutHandlerNames.add("securityContextLogoutHandler");
 
-		getOrderedFilters().clear();
+		providerNames.clear();
+		providerNames.add("daoAuthenticationProvider");
+		providerNames.add("anonymousAuthenticationProvider");
+		providerNames.add("rememberMeAuthenticationProvider");
 
-		getConfiguredOrderedFilters().clear();
-	}
-
-	private static Object getFromContext(String key) {
-		return _context.get(key);
-	}
+		orderedFilters.clear();
 
-	private static void storeInContext(String key, Object value) {
-		_context.put(key, value);
+		configuredOrderedFilters.clear();
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/access/vote/AuthenticatedVetoableDecisionManager.java b/src/java/grails/plugin/springsecurity/access/vote/AuthenticatedVetoableDecisionManager.java
index 799a9c473..e50a094a5 100644
--- a/src/java/grails/plugin/springsecurity/access/vote/AuthenticatedVetoableDecisionManager.java
+++ b/src/java/grails/plugin/springsecurity/access/vote/AuthenticatedVetoableDecisionManager.java
@@ -25,12 +25,12 @@
 import org.springframework.security.core.Authentication;
 
 /**
-* Uses the affirmative-based logic for roles, i.e. any in the list will grant access, but allows
-* an authenticated voter to 'veto' access. This allows specification of roles and
-* IS_AUTHENTICATED_FULLY on one line in SecurityConfig.groovy.
-*
+ * Uses the affirmative-based logic for roles, i.e. any in the list will grant access, but allows
+ * an authenticated voter to 'veto' access. This allows specification of roles and
+ * IS_AUTHENTICATED_FULLY on one line in SecurityConfig.groovy.
+ *
  * @author Burt Beckwith
-*/
+ */
 public class AuthenticatedVetoableDecisionManager extends AbstractAccessDecisionManager {
 
 	/**
@@ -53,7 +53,8 @@ public void decide(final Authentication authentication, final Object object, fin
 	 * throw an exception; if any grant, return true;
 	 * otherwise return false if all abstain.
 	 */
-	private boolean checkAuthenticatedVoters(final Authentication authentication, final Object object,
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	protected boolean checkAuthenticatedVoters(final Authentication authentication, final Object object,
 			final Collection configAttributes) {
 
 		boolean grant = false;
@@ -80,7 +81,8 @@ private boolean checkAuthenticatedVoters(final Authentication authentication, fi
 	 * return true. If any voter denies, throw exception. Otherwise return false
 	 * to indicate that all abstained.
 	 */
-	private boolean checkOtherVoters(Authentication authentication, Object object, Collection configAttributes) {
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	protected boolean checkOtherVoters(Authentication authentication, Object object, Collection configAttributes) {
 		int denyCount = 0;
 		for (AccessDecisionVoter voter : getDecisionVoters()) {
 			if (voter instanceof AuthenticatedVoter) {
@@ -107,7 +109,7 @@ private boolean checkOtherVoters(Authentication authentication, Object object, C
 		return false;
 	}
 
-	private void deny() {
+	protected void deny() {
 		throw new AccessDeniedException(messages.getMessage(
 				"AbstractAccessDecisionManager.accessDenied",
 				"Access is denied"));
diff --git a/src/java/grails/plugin/springsecurity/authentication/encoding/DigestAuthPasswordEncoder.java b/src/java/grails/plugin/springsecurity/authentication/encoding/DigestAuthPasswordEncoder.java
index dfe771d93..405c1f807 100644
--- a/src/java/grails/plugin/springsecurity/authentication/encoding/DigestAuthPasswordEncoder.java
+++ b/src/java/grails/plugin/springsecurity/authentication/encoding/DigestAuthPasswordEncoder.java
@@ -18,8 +18,7 @@
 import java.security.NoSuchAlgorithmException;
 
 import org.springframework.beans.factory.InitializingBean;
-import org.springframework.security.authentication.encoding.PasswordEncoder;
-import org.springframework.security.core.codec.Hex;
+import org.springframework.security.crypto.codec.Hex;
 import org.springframework.util.Assert;
 
 /**
@@ -34,9 +33,10 @@
  *
  * @author Burt Beckwith
  */
-public class DigestAuthPasswordEncoder implements PasswordEncoder, InitializingBean {
+@SuppressWarnings("deprecation")
+public class DigestAuthPasswordEncoder implements org.springframework.security.authentication.encoding.PasswordEncoder, InitializingBean {
 
-	private String _realm;
+	protected String realm;
 
 	/**
 	 * {@inheritDoc}
@@ -46,7 +46,7 @@ public class DigestAuthPasswordEncoder implements PasswordEncoder, InitializingB
 	public String encodePassword(final String rawPass, final Object salt) {
 		Assert.notNull(salt, "Salt is required and must be the username");
 		String username = salt.toString();
-		return md5Hex(username + ":" + _realm + ":" + rawPass);
+		return md5Hex(username + ":" + realm + ":" + rawPass);
 	}
 
 	/**
@@ -62,10 +62,10 @@ public boolean isPasswordValid(final String encPass, final String rawPass, final
 	/**
 	 * Dependency injection for the realm name.
 	 *
-	 * @param realm the name
+	 * @param name the name
 	 */
-	public void setRealm(final String realm) {
-		_realm = realm;
+	public void setRealm(final String name) {
+		realm = name;
 	}
 
 	/**
@@ -73,10 +73,10 @@ public void setRealm(final String realm) {
 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
 	 */
 	public void afterPropertiesSet() {
-		Assert.hasLength(_realm, "realm is required");
+		Assert.hasLength(realm, "realm is required");
 	}
 
-	private String md5Hex(final String s) {
+	protected String md5Hex(final String s) {
 		MessageDigest digest;
 		try {
 			digest = MessageDigest.getInstance("MD5");
diff --git a/src/java/grails/plugin/springsecurity/userdetails/DefaultPostAuthenticationChecks.java b/src/java/grails/plugin/springsecurity/userdetails/DefaultPostAuthenticationChecks.java
index ac83bc189..8c911350c 100644
--- a/src/java/grails/plugin/springsecurity/userdetails/DefaultPostAuthenticationChecks.java
+++ b/src/java/grails/plugin/springsecurity/userdetails/DefaultPostAuthenticationChecks.java
@@ -39,7 +39,7 @@ public void check(UserDetails user) {
 
 			throw new CredentialsExpiredException(messages.getMessage(
 					"AbstractUserDetailsAuthenticationProvider.credentialsExpired",
-					"User credentials have expired"), user);
+					"User credentials have expired"));
 		}
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/userdetails/DefaultPreAuthenticationChecks.java b/src/java/grails/plugin/springsecurity/userdetails/DefaultPreAuthenticationChecks.java
index 8f8bcd787..20a5e97bd 100644
--- a/src/java/grails/plugin/springsecurity/userdetails/DefaultPreAuthenticationChecks.java
+++ b/src/java/grails/plugin/springsecurity/userdetails/DefaultPreAuthenticationChecks.java
@@ -40,21 +40,21 @@ public void check(UserDetails user) {
 			log.debug("User account is locked");
 
 			throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
-				"User account is locked"), user);
+				"User account is locked"));
 		}
 
 		if (!user.isEnabled()) {
 			log.debug("User account is disabled");
 
 			throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
-				"User is disabled"), user);
+				"User is disabled"));
 		}
 
 		if (!user.isAccountNonExpired()) {
 			log.debug("User account is expired");
 
 			throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
-				"User account has expired"), user);
+				"User account has expired"));
 		}
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/access/AjaxAwareAccessDeniedHandler.java b/src/java/grails/plugin/springsecurity/web/access/AjaxAwareAccessDeniedHandler.java
index a9e246d4d..00c14231b 100644
--- a/src/java/grails/plugin/springsecurity/web/access/AjaxAwareAccessDeniedHandler.java
+++ b/src/java/grails/plugin/springsecurity/web/access/AjaxAwareAccessDeniedHandler.java
@@ -30,7 +30,7 @@
 import org.springframework.security.web.PortResolver;
 import org.springframework.security.web.WebAttributes;
 import org.springframework.security.web.access.AccessDeniedHandler;
-import org.springframework.security.web.savedrequest.DefaultSavedRequest;
+import org.springframework.security.web.savedrequest.RequestCache;
 import org.springframework.util.Assert;
 
 /**
@@ -38,10 +38,11 @@
  */
 public class AjaxAwareAccessDeniedHandler implements AccessDeniedHandler, InitializingBean {
 
-	private String errorPage;
-	private String ajaxErrorPage;
-	private PortResolver portResolver;
-	private AuthenticationTrustResolver authenticationTrustResolver;
+	protected String errorPage;
+	protected String ajaxErrorPage;
+	protected PortResolver portResolver;
+	protected AuthenticationTrustResolver authenticationTrustResolver;
+	protected RequestCache requestCache;
 
 	/**
 	 * {@inheritDoc}
@@ -54,8 +55,8 @@ public void handle(final HttpServletRequest request, final HttpServletResponse r
 
 		if (e != null && isLoggedIn() && authenticationTrustResolver.isRememberMe(getAuthentication())) {
 			// user has a cookie but is getting bounced because of IS_AUTHENTICATED_FULLY,
-			// so Acegi won't save the original request
-			request.getSession().setAttribute(WebAttributes.SAVED_REQUEST, new DefaultSavedRequest(request, portResolver));
+			// so Spring Security won't save the original request
+			requestCache.saveRequest(request, response);
 		}
 
 		if (response.isCommitted()) {
@@ -93,12 +94,12 @@ else if (errorPage != null) {
 		response.sendRedirect(response.encodeRedirectURL(redirectUrl));
 	}
 
-	private Authentication getAuthentication() {
+	protected Authentication getAuthentication() {
 		return SecurityContextHolder.getContext() == null ? null :
 		       SecurityContextHolder.getContext().getAuthentication();
 	}
 
-	private boolean isLoggedIn() {
+	protected boolean isLoggedIn() {
 		Authentication authentication = getAuthentication();
 		if (authentication == null) {
 			return false;
@@ -140,6 +141,14 @@ public void setAuthenticationTrustResolver(final AuthenticationTrustResolver res
 		authenticationTrustResolver = resolver;
 	}
 
+	/**
+	 * Dependency injection for the request cache.
+	 * @param cache the cache
+	 */
+	public void setRequestCache(RequestCache cache) {
+		requestCache = cache;
+	}
+
 	/**
 	 * {@inheritDoc}
 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
@@ -147,5 +156,6 @@ public void setAuthenticationTrustResolver(final AuthenticationTrustResolver res
 	public void afterPropertiesSet() {
 		Assert.notNull(portResolver, "portResolver is required");
 		Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver is required");
+		Assert.notNull(requestCache, "requestCache is required");
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/access/GrailsWebInvocationPrivilegeEvaluator.java b/src/java/grails/plugin/springsecurity/web/access/GrailsWebInvocationPrivilegeEvaluator.java
index 86e7e3e08..40a3113c1 100644
--- a/src/java/grails/plugin/springsecurity/web/access/GrailsWebInvocationPrivilegeEvaluator.java
+++ b/src/java/grails/plugin/springsecurity/web/access/GrailsWebInvocationPrivilegeEvaluator.java
@@ -32,6 +32,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.ConfigAttribute;
 import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
@@ -49,23 +51,25 @@
  */
 public class GrailsWebInvocationPrivilegeEvaluator extends DefaultWebInvocationPrivilegeEvaluator {
 
-	private static final FilterChain DUMMY_CHAIN = new FilterChain() {
+	protected static final FilterChain DUMMY_CHAIN = new FilterChain() {
 		public void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException {
 			throw new UnsupportedOperationException("GrailsWebInvocationPrivilegeEvaluator does not support filter chains");
 		}
 	};
 
-	private static final HttpServletResponse DUMMY_RESPONSE = DummyResponseCreator.createInstance();
+	protected static final HttpServletResponse DUMMY_RESPONSE = DummyResponseCreator.createInstance();
 
-	private AbstractSecurityInterceptor _interceptor;
+	protected final Logger log = LoggerFactory.getLogger(getClass());
+
+	protected AbstractSecurityInterceptor interceptor;
 
 	/**
 	 * Constructor.
-	 * @param interceptor the security interceptor
+	 * @param securityInterceptor the security interceptor
 	 */
-	public GrailsWebInvocationPrivilegeEvaluator(final AbstractSecurityInterceptor interceptor) {
-		super(interceptor);
-		_interceptor = interceptor;
+	public GrailsWebInvocationPrivilegeEvaluator(final AbstractSecurityInterceptor securityInterceptor) {
+		super(securityInterceptor);
+		interceptor = securityInterceptor;
 	}
 
 	@Override
@@ -78,9 +82,9 @@ public boolean isAllowed(String contextPath, final String uri, final String meth
 
 		FilterInvocation fi = createFilterInvocation(contextPath, uri, method);
 
-		Collection attrs = _interceptor.obtainSecurityMetadataSource().getAttributes(fi);
+		Collection attrs = interceptor.obtainSecurityMetadataSource().getAttributes(fi);
 		if (attrs == null) {
-			return !_interceptor.isRejectPublicInvocations();
+			return !interceptor.isRejectPublicInvocations();
 		}
 
 		if (authentication == null) {
@@ -88,13 +92,13 @@ public boolean isAllowed(String contextPath, final String uri, final String meth
 		}
 
 		try {
-			_interceptor.getAccessDecisionManager().decide(authentication, fi, attrs);
+			interceptor.getAccessDecisionManager().decide(authentication, fi, attrs);
 			return true;
 		}
 		catch (AccessDeniedException unauthorized) {
-			if (logger.isDebugEnabled()) {
+			if (log.isDebugEnabled()) {
 				GrailsUtil.deepSanitize(unauthorized);
-				logger.debug(fi + " denied for " + authentication, unauthorized);
+				log.debug(fi + " denied for " + authentication, unauthorized);
 			}
 			return false;
 		}
@@ -107,7 +111,7 @@ protected FilterInvocation createFilterInvocation(final String contextPath, fina
 	}
 }
 
-class DummyRequestCreator { //implements HttpServletRequest {
+class DummyRequestCreator {
 
 	static HttpServletRequest createInstance(final String contextPath, final String httpMethod, final String requestURI) {
 		final Map attributes = new HashMap();
diff --git a/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckInsecureChannelProcessor.java b/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckInsecureChannelProcessor.java
index f058bdb58..565239591 100644
--- a/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckInsecureChannelProcessor.java
+++ b/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckInsecureChannelProcessor.java
@@ -29,8 +29,8 @@
  */
 public class HeaderCheckInsecureChannelProcessor extends InsecureChannelProcessor {
 
-	private String headerName;
-	private String headerValue;
+	protected String headerName;
+	protected String headerValue;
 
 	@Override
 	public void decide(FilterInvocation invocation, Collection config)
diff --git a/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckSecureChannelProcessor.java b/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckSecureChannelProcessor.java
index d9aa6ac06..acc191f16 100644
--- a/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckSecureChannelProcessor.java
+++ b/src/java/grails/plugin/springsecurity/web/access/channel/HeaderCheckSecureChannelProcessor.java
@@ -29,8 +29,8 @@
  */
 public class HeaderCheckSecureChannelProcessor extends SecureChannelProcessor {
 
-	private String headerName;
-	private String headerValue;
+	protected String headerName;
+	protected String headerValue;
 
 	@Override
 	public void decide(FilterInvocation invocation, Collection config)
diff --git a/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionConfigAttribute.java b/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionConfigAttribute.java
index ee6b82596..75df7c0a1 100644
--- a/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionConfigAttribute.java
+++ b/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionConfigAttribute.java
@@ -26,16 +26,16 @@
  */
 public class WebExpressionConfigAttribute implements ConfigAttribute {
 
-	private static final long serialVersionUID = 1L;
+	private static final long serialVersionUID = 1;
 
-	private final Expression _authorizeExpression;
+	protected final Expression expression;
 
 	/**
 	 * Constructor.
 	 * @param authorizeExpression the expression
 	 */
 	public WebExpressionConfigAttribute(final Expression authorizeExpression) {
-		_authorizeExpression = authorizeExpression;
+		expression = authorizeExpression;
 	}
 
 	/**
@@ -43,7 +43,7 @@ public WebExpressionConfigAttribute(final Expression authorizeExpression) {
 	 * @return the expression
 	 */
 	public Expression getAuthorizeExpression() {
-		return _authorizeExpression;
+		return expression;
 	}
 
 	/**
@@ -56,6 +56,6 @@ public String getAttribute() {
 
 	@Override
 	public String toString() {
-		return _authorizeExpression.getExpressionString();
+		return expression.getExpressionString();
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionVoter.java b/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionVoter.java
index a88fb8afb..7724503f8 100644
--- a/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionVoter.java
+++ b/src/java/grails/plugin/springsecurity/web/access/expression/WebExpressionVoter.java
@@ -20,31 +20,25 @@
 import org.springframework.security.access.AccessDecisionVoter;
 import org.springframework.security.access.ConfigAttribute;
 import org.springframework.security.access.expression.ExpressionUtils;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.FilterInvocation;
-import org.springframework.security.web.access.expression.WebSecurityExpressionHandler;
 import org.springframework.util.Assert;
 
 /**
  * Based on the class of the same name in Spring Security which uses the
  * package-default WebExpressionConfigAttribute.
  *
+ * @author Luke Taylor
  * @author Burt Beckwith
  */
-public class WebExpressionVoter implements AccessDecisionVoter {
+public class WebExpressionVoter implements AccessDecisionVoter {
 
-	private WebSecurityExpressionHandler _expressionHandler;
-
-	/**
-	 * {@inheritDoc}
-	 * @see org.springframework.security.access.AccessDecisionVoter#vote(
-	 * 	org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
-	 */
-	public int vote(final Authentication authentication, final Object object,
-			final Collection attributes) {
+   protected SecurityExpressionHandler expressionHandler;
 
+	public int vote(Authentication authentication, FilterInvocation fi, Collection attributes) {
 		Assert.notNull(authentication, "authentication cannot be null");
-		Assert.notNull(object, "object cannot be null");
+		Assert.notNull(fi, "object cannot be null");
 		Assert.notNull(attributes, "attributes cannot be null");
 
 		WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
@@ -52,14 +46,12 @@ public int vote(final Authentication authentication, final Object object,
 			return ACCESS_ABSTAIN;
 		}
 
-		FilterInvocation fi = (FilterInvocation)object;
-		EvaluationContext ctx = _expressionHandler.createEvaluationContext(authentication, fi);
+		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, fi);
 
-		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ?
-				ACCESS_GRANTED : ACCESS_DENIED;
+		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED;
 	}
 
-	private WebExpressionConfigAttribute findConfigAttribute(final Collection attributes) {
+	protected WebExpressionConfigAttribute findConfigAttribute(Collection attributes) {
 		for (ConfigAttribute attribute : attributes) {
 			if (attribute instanceof WebExpressionConfigAttribute) {
 				return (WebExpressionConfigAttribute)attribute;
@@ -68,28 +60,19 @@ private WebExpressionConfigAttribute findConfigAttribute(final Collection clazz) {
 		return clazz.isAssignableFrom(FilterInvocation.class);
 	}
 
 	/**
 	 * Dependency injection for the expression handler.
-	 * @param expressionHandler the handler
+	 * @param handler the handler
 	 */
-	public void setExpressionHandler(final WebSecurityExpressionHandler expressionHandler) {
-		_expressionHandler = expressionHandler;
+	public void setExpressionHandler(SecurityExpressionHandler handler) {
+		expressionHandler = handler;
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationEntryPoint.java b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationEntryPoint.java
index 1eba72cec..33ec8c141 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationEntryPoint.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationEntryPoint.java
@@ -28,7 +28,15 @@
  */
 public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
 
-	private String ajaxLoginFormUrl;
+	protected String ajaxLoginFormUrl;
+
+	/**
+	 * @param loginFormUrl URL where the login page can be found. Should either be relative to the web-app context path
+	 * (include a leading {@code /}) or an absolute URL.
+	 */
+	public AjaxAwareAuthenticationEntryPoint(String loginFormUrl) {
+		super(loginFormUrl);
+	}
 
 	@Override
 	protected String determineUrlToUseForThisRequest(final HttpServletRequest request,
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationFailureHandler.java b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationFailureHandler.java
index 414e1280e..a5d4df84f 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationFailureHandler.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationFailureHandler.java
@@ -34,7 +34,7 @@
  */
 public class AjaxAwareAuthenticationFailureHandler extends ExceptionMappingAuthenticationFailureHandler implements InitializingBean {
 
-	private String _ajaxAuthenticationFailureUrl;
+	protected String ajaxAuthenticationFailureUrl;
 
 	/**
 	 * {@inheritDoc}
@@ -48,7 +48,7 @@ public void onAuthenticationFailure(final HttpServletRequest request, final Http
 
 		if (SpringSecurityUtils.isAjax(request)) {
 			saveException(request, exception);
-			getRedirectStrategy().sendRedirect(request, response, _ajaxAuthenticationFailureUrl);
+			getRedirectStrategy().sendRedirect(request, response, ajaxAuthenticationFailureUrl);
 		}
 		else {
 			super.onAuthenticationFailure(request, response, exception);
@@ -60,7 +60,7 @@ public void onAuthenticationFailure(final HttpServletRequest request, final Http
 	 * @param url the url
 	 */
 	public void setAjaxAuthenticationFailureUrl(final String url) {
-		_ajaxAuthenticationFailureUrl = url;
+		ajaxAuthenticationFailureUrl = url;
 	}
 
 	/**
@@ -68,6 +68,6 @@ public void setAjaxAuthenticationFailureUrl(final String url) {
 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
 	 */
 	public void afterPropertiesSet() {
-		Assert.notNull(_ajaxAuthenticationFailureUrl, "ajaxAuthenticationFailureUrl is required");
+		Assert.notNull(ajaxAuthenticationFailureUrl, "ajaxAuthenticationFailureUrl is required");
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationSuccessHandler.java b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationSuccessHandler.java
index 6b8d202f9..c9a91a9b6 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationSuccessHandler.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/AjaxAwareAuthenticationSuccessHandler.java
@@ -31,8 +31,8 @@
  */
 public class AjaxAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
 
-	private String _ajaxSuccessUrl;
-	private RequestCache _requestCache;
+	protected String ajaxSuccessUrl;
+	protected RequestCache requestCache;
 
 	/**
 	 * {@inheritDoc}
@@ -42,17 +42,17 @@ public class AjaxAwareAuthenticationSuccessHandler extends SavedRequestAwareAuth
 	@Override
 	protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
 		if (SpringSecurityUtils.isAjax(request)) {
-			return _ajaxSuccessUrl;
+			return ajaxSuccessUrl;
 		}
 		return super.determineTargetUrl(request, response);
 	}
 
 	/**
 	 * Dependency injection for the Ajax success url, e.g. '/login/ajaxSuccess'
-	 * @param ajaxSuccessUrl the url
+	 * @param url the url
 	 */
-	public void setAjaxSuccessUrl(final String ajaxSuccessUrl) {
-		_ajaxSuccessUrl = ajaxSuccessUrl;
+	public void setAjaxSuccessUrl(final String url) {
+		ajaxSuccessUrl = url;
 	}
 
 	/**
@@ -64,9 +64,13 @@ public void setAjaxSuccessUrl(final String ajaxSuccessUrl) {
 	@Override
 	public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
 			final Authentication authentication) throws ServletException, IOException {
-		super.onAuthenticationSuccess(request, response, authentication);
-		// always remove the saved request
-		_requestCache.removeRequest(request, response);
+		try {
+			super.onAuthenticationSuccess(request, response, authentication);
+		}
+		finally {
+			// always remove the saved request
+			requestCache.removeRequest(request, response);
+		}
 	}
 
 	/**
@@ -75,8 +79,8 @@ public void onAuthenticationSuccess(final HttpServletRequest request, final Http
 	 * 	org.springframework.security.web.savedrequest.RequestCache)
 	 */
 	@Override
-	public void setRequestCache(RequestCache requestCache) {
-		super.setRequestCache(requestCache);
-		_requestCache = requestCache;
+	public void setRequestCache(RequestCache cache) {
+		super.setRequestCache(cache);
+		requestCache = cache;
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/NullLogoutHandlerRememberMeServices.java b/src/java/grails/plugin/springsecurity/web/authentication/NullLogoutHandlerRememberMeServices.java
index 6b591bb01..386262dff 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/NullLogoutHandlerRememberMeServices.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/NullLogoutHandlerRememberMeServices.java
@@ -28,10 +28,10 @@ public class NullLogoutHandlerRememberMeServices extends NullRememberMeServices
 
 	/**
 	 * {@inheritDoc}
-	 * @see org.springframework.security.web.authentication.logout.LogoutHandler#logout(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication)
+	 * @see org.springframework.security.web.authentication.logout.LogoutHandler#logout(
+	 * 	javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication)
 	 */
-	public void logout(final HttpServletRequest request, final HttpServletResponse response,
-			final Authentication authentication) {
+	public void logout(final HttpServletRequest req, final HttpServletResponse res, final Authentication a) {
 		// no-op
 	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/RequestHolderAuthenticationFilter.java b/src/java/grails/plugin/springsecurity/web/authentication/RequestHolderAuthenticationFilter.java
index ebd292ec7..0a34ea833 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/RequestHolderAuthenticationFilter.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/RequestHolderAuthenticationFilter.java
@@ -14,6 +14,7 @@
  */
 package grails.plugin.springsecurity.web.authentication;
 
+import grails.plugin.springsecurity.SpringSecurityUtils;
 import grails.plugin.springsecurity.web.SecurityRequestHolder;
 
 import java.io.IOException;
@@ -24,8 +25,12 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.util.Assert;
 
 /**
  * Extends the default {@link UsernamePasswordAuthenticationFilter} to store the request
@@ -35,6 +40,8 @@
  */
 public class RequestHolderAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
 
+	protected Boolean storeLastUsername;
+
 	@Override
 	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
 		SecurityRequestHolder.set((HttpServletRequest)request, (HttpServletResponse)response);
@@ -45,4 +52,37 @@ public void doFilter(final ServletRequest request, final ServletResponse respons
 			SecurityRequestHolder.reset();
 		}
 	}
+
+	@Override
+	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+
+		if (storeLastUsername) {
+			// Place the last username attempted into HttpSession for views
+			HttpSession session = request.getSession(false);
+			if (session != null || getAllowSessionCreation()) {
+				String username = obtainUsername(request);
+				if (username == null) {
+					username = "";
+				}
+				username = username.trim();
+				session.setAttribute(SpringSecurityUtils.SPRING_SECURITY_LAST_USERNAME_KEY, username); // TODO doc that not escaped now
+			}
+		}
+
+		return super.attemptAuthentication(request, response);
+	}
+
+	/**
+	 * Whether to store the last attempted username in the session.
+	 * @param storeLastUsername store if true
+	 */
+	public void setStoreLastUsername(Boolean storeLastUsername) {
+		this.storeLastUsername = storeLastUsername;
+	}
+
+	@Override
+	public void afterPropertiesSet() {
+		super.afterPropertiesSet();
+		Assert.notNull(storeLastUsername, "storeLastUsername must be set");
+	}
 }
diff --git a/src/java/grails/plugin/springsecurity/web/authentication/logout/MutableLogoutFilter.java b/src/java/grails/plugin/springsecurity/web/authentication/logout/MutableLogoutFilter.java
index 2ca7cc04e..cc1c8541b 100644
--- a/src/java/grails/plugin/springsecurity/web/authentication/logout/MutableLogoutFilter.java
+++ b/src/java/grails/plugin/springsecurity/web/authentication/logout/MutableLogoutFilter.java
@@ -24,6 +24,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.authentication.logout.LogoutFilter;
@@ -35,17 +37,18 @@
  */
 public class MutableLogoutFilter extends LogoutFilter {
 
-	private final LogoutSuccessHandler _logoutSuccessHandler;
+	protected final LogoutSuccessHandler logoutSuccessHandler;
+	protected final Logger log = LoggerFactory.getLogger(getClass());
 
-	private List _handlers;
+	protected List handlers;
 
 	/**
 	 * Constructor.
-	 * @param logoutSuccessHandler the logout success handler
+	 * @param successHandler the logout success handler
 	 */
-	public MutableLogoutFilter(LogoutSuccessHandler logoutSuccessHandler) {
-		super(logoutSuccessHandler, new DummyLogoutHandler());
-		_logoutSuccessHandler = logoutSuccessHandler;
+	public MutableLogoutFilter(LogoutSuccessHandler successHandler) {
+		super(successHandler, new DummyLogoutHandler());
+		logoutSuccessHandler = successHandler;
 	}
 
 	/**
@@ -63,15 +66,15 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final
 		if (requiresLogout(request, response)) {
 			Authentication auth = SecurityContextHolder.getContext().getAuthentication();
 
-			if (logger.isDebugEnabled()) {
-				logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
+			if (log.isDebugEnabled()) {
+				log.debug("Logging out user '{0}' and transferring to logout destination", auth);
 			}
 
-			for (LogoutHandler handler : _handlers) {
+			for (LogoutHandler handler : handlers) {
 				handler.logout(request, response, auth);
 			}
 
-			_logoutSuccessHandler.onLogoutSuccess(request, response, auth);
+			logoutSuccessHandler.onLogoutSuccess(request, response, auth);
 
 			return;
 		}
@@ -81,17 +84,17 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final
 
 	/**
 	 * Dependency injection for the logout handlers.
-	 * @param handlers the handlers
+	 * @param l the handlers
 	 */
-	public void setHandlers(final List handlers) {
-		_handlers = handlers;
+	public void setHandlers(final List l) {
+		handlers = l;
 	}
 
 	/**
 	 * Null logout handler that's used to provide a non-empty list of handlers to the base class.
 	 * The real handlers will be after construction.
 	 */
-	private static class DummyLogoutHandler implements LogoutHandler {
+	protected static class DummyLogoutHandler implements LogoutHandler {
 		public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
 			// do nothing
 		}
diff --git a/src/templates/Person.groovy.template b/src/templates/Person.groovy.template
index 2ca64a9aa..c11e76133 100644
--- a/src/templates/Person.groovy.template
+++ b/src/templates/Person.groovy.template
@@ -2,7 +2,8 @@ package ${packageName}
 
 class ${userClassName} {
 
-${dependencyInjections}
+	transient springSecurityService
+
 	String username
 	String password
 	boolean enabled
@@ -10,6 +11,8 @@ ${dependencyInjections}
 	boolean accountLocked
 	boolean passwordExpired
 
+	static transients = ['springSecurityService']
+
 	static constraints = {
 		username blank: false, unique: true
 		password blank: false
@@ -35,5 +38,5 @@ ${dependencyInjections}
 
 	protected void encodePassword() {
 		password = springSecurityService.encodePassword(password)
-	}${dirtyMethods}
+	}
 }