Hacking the Grails Spring  Security Plugins Burt Beckwith SpringSource
CONFIDENTIAL 2
web.xml CONFIDENTIAL 3
web.xml  Spring Security is implemented as a filter chain, so the <filter-mapping> elements are the important ones: • charEncodingFilter • hiddenHttpMethod • grailsWebRequest • springSecurityFilterChain • reloadFilter • sitemesh • urlMapping CONFIDENTIAL 4
web.xml  Spring Security is implemented as a filter chain, so the <filter-mapping> elements are the important ones: • charEncodingFilter • hiddenHttpMethod • grailsWebRequest • springSecurityFilterChain • reloadFilter • sitemesh • urlMapping CONFIDENTIAL 5
web.xml - charEncodingFilter  org.springframework.web.filter.DelegatingFilterProxy  Uses the "characterEncodingFilter" bean (org.springframework.web.filter.CharacterEncodingFilter) defined in applicationContext.xml (the “parent” context)  request.setCharacterEncoding("utf-8");  response.setCharacterEncoding("utf-8"); CONFIDENTIAL 6
web.xml - hiddenHttpMethod  org.codehaus.groovy.grails.web.filters.HiddenHttpMethodFilter String httpMethod = getHttpMethodOverride(request); filterChain.doFilter( new HttpMethodRequestWrapper(httpMethod, request), response);  <g:form controller="book" method="DELETE">  See section “13.1 REST” in the docs CONFIDENTIAL 7
web.xml - grailsWebRequest  org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter LocaleContextHolder.setLocale(request.getLocale()); GrailsWebRequest webRequest = new GrailsWebRequest( request, response, getServletContext()); configureParameterCreationListeners(webRequest); try { WebUtils.storeGrailsWebRequest(webRequest); // Set the flash scope instance to its next state FlashScope fs = webRequest.getAttributes().getFlashScope(request); fs.next(); filterChain.doFilter(request, response); } finally { webRequest.requestCompleted(); WebUtils.clearGrailsWebRequest(); LocaleContextHolder.setLocale(null); } CONFIDENTIAL 8
web.xml - springSecurityFilterChain  org.springframework.web.filter.DelegatingFilterProxy  Uses the "springSecurityFilterChain" bean defined by the plugin (org.springframework.security.web.FilterChainProxy) CONFIDENTIAL 9
web.xml - springSecurityFilterChain  Typical filter beans: • securityContextPersistenceFilter • logoutFilter • authenticationProcessingFilter • securityContextHolderAwareRequestFilter • rememberMeAuthenticationFilter • anonymousAuthenticationFilter • exceptionTranslationFilter • filterInvocationInterceptor CONFIDENTIAL 10
web.xml - reloadFilter  org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter  No longer used in 2.0+  “Copies resources from the source on content change and manages reloading if necessary.” GrailsApplication application = (GrailsApplication)context.getBean( GrailsApplication.APPLICATION_ID); if (!application.isInitialised()) { application.rebuild(); GrailsRuntimeConfigurator config = new GrailsRuntimeConfigurator( application, parent); config.reconfigure(context, getServletContext(), true); } CONFIDENTIAL 11
web.xml - sitemesh  org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter  Subclass of com.opensymphony.sitemesh.webapp.SiteMeshFilter  Renders Sitemesh templates/layouts CONFIDENTIAL 12
web.xml - urlMapping  org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter  Matches requested url to the correct controller/action/view/error code  Based on mappings in grails-app/conf/UrlMappings.groovy CONFIDENTIAL 13
springSecurityFilterChain CONFIDENTIAL 14
springSecurityFilterChain - securityContextPersistenceFilter  org.springframework.security.web.context.SecurityContextPersistenceFilter  Retrieves the SecurityContext from the HTTP session (under the ”SPRING_SECURITY_CONTEXT” key) or creates a new empty one  Stores the context in the holder: SecurityContextHolder.setContext()  Removes the context after request CONFIDENTIAL 15
springSecurityFilterChain - logoutFilter  org.codehaus.groovy.grails.plugins.springsecurity.MutableLogoutFilter  If uri is ”/j_spring_security_logout” calls handler.logout(request, response, auth) for each o.s.s.web.authentication.logout.LogoutHandler and redirects to post- logout url (by default ”/”) CONFIDENTIAL 16
springSecurityFilterChain - authenticationProcessingFilter  org.codehaus.groovy.grails.plugins.springsecurity.RequestHolderAuthenticationFilter (extends o.s.s.web.authentication.UsernamePasswordAuthenticationFilter)  Sets the request and response in the SecurityRequestHolder ThreadLocal fields (custom plugin functionality)  If uri is ”/j_spring_security_check” calls attemptAuthentication() CONFIDENTIAL 17
springSecurityFilterChain - securityContextHolderAwareRequestFilter  o.s.s.web.servletapi.SecurityContextHolderAwareRequestFilter  Configures a request wrapper which implements the servlet API security methods (getRemoteUser, getUserPrincipal, isUserInRole) chain.doFilter(new SecurityContextHolderAwareRequestWrapper( request, rolePrefix), response); CONFIDENTIAL 18
springSecurityFilterChain - rememberMeAuthenticationFilter  o.s.s.web.authentication.rememberme.RememberMeAuthenticationFilter  If not logged in, attempt to login from the remember-me cookie CONFIDENTIAL 19
springSecurityFilterChain - anonymousAuthenticationFilter  o.s.s.web.authentication.AnonymousAuthenticationFilter  If not logged in creates an AnonymousAuthenticationToken and registers it as the Authentication in the SecurityContext https://secure.flickr.com/photos/gaelx/5445598436/ CONFIDENTIAL 20
springSecurityFilterChain - exceptionTranslationFilter  o.s.s.web.access.ExceptionTranslationFilter  Handles AccessDeniedException and AuthenticationException  For AuthenticationException will invoke the authenticationEntryPoint  For AccessDeniedException invoke the authenticationEntryPoint if anonymous, otherwise delegate to o.s.s.web.access.AccessDeniedHandler CONFIDENTIAL 21
springSecurityFilterChain - filterInvocationInterceptor  o.s.s.web.access.intercept.FilterSecurityInterceptor  Determines the o.s.s.access.SecurityConfig collection for the request from FilterInvocationSecurityMetadataSource (AnnotationFilterInvocationDefinition, InterceptUrlMapFilterInvocationDefinition, or RequestmapFilterInvocationDefinition) CONFIDENTIAL 22
springSecurityFilterChain - filterInvocationInterceptor  Delegates to an AccessDecisionManager (org.codehaus.groovy.grails.plugins.springsecurity. AuthenticatedVetoableDecisionManager) to call each registered o.s.s.access.AccessDecisionVoter CONFIDENTIAL 23
springSecurityFilterChain - filterInvocationInterceptor  o.s.s.access.vote.AuthenticatedVoter works with “IS_AUTHENTICATED_FULLY”, “IS_AUTHENTICATED_REMEMBERED”, and “IS_AUTHENTICATED_ANONYMOUSLY” CONFIDENTIAL 24
springSecurityFilterChain - filterInvocationInterceptor  o.s.s.access.vote.RoleHierarchyVoter finds the GrantedAuthority instances from the Authentication, checks for matches with required roles CONFIDENTIAL 25
springSecurityFilterChain - filterInvocationInterceptor  org.codehaus.groovy.grails.plugins.springsecurity. WebExpressionVoter looks for a WebExpressionConfigAttribute and evaluates its expression if found CONFIDENTIAL 26
springSecurityFilterChain - filterInvocationInterceptor Common built-in expressions Expression Description hasRole([role]) Returns true if the current principal has the specified role. hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings) principal Allows direct access to the principal object representing the current user authentication Allows direct access to the current Authentication object obtained from the SecurityContext permitAll Always evaluates to true denyAll Always evaluates to false isAnonymous() Returns true if the current principal is an anonymous user isRememberMe() Returns true if the current principal is a remember-me user isAuthenticated() Returns true if the user is not anonymous isFullyAuthenticated() Returns true if the user is not an anonymous or a remember-me user CONFIDENTIAL 27
springSecurityFilterChain - filterInvocationInterceptor if (denyCount > 0) { throw new AccessDeniedException("Access is denied"); }  this is caught by the exceptionTranslationFilter  If the Authentication is anonymous, stores a SavedRequest in the HTTP session and calls authenticationEntryPoint.commence( request, response, reason)  The authenticationEntryPoint is an org.codehaus.groovy.grails.plugins.springsecurity. AjaxAwareAuthenticationEntryPoint  This issues a redirect to the login page, by default /login/auth CONFIDENTIAL 28
Customizing the Plugin CONFIDENTIAL 29
Customizing the Plugin Traditional Spring Security uses XML with namespaced beans: <beans:beans xmlns="http://www.springframework.org/schema/security" ...> <http auto-config="true"> <intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" /> </authentication-provider> </authentication-manager> </beans:beans> CONFIDENTIAL 30
Customizing the Plugin  Simple; not always clear how to customize; many options aren't exposed  In contrast, the plugin explicitly defines every bean  See SpringSecurityCoreGrailsPlugin.groovy for the details CONFIDENTIAL 31
Overriding Beans CONFIDENTIAL 32
Overriding Beans accessDecisionManager filterInvocationInterceptor roleVoter accessDeniedHandler logoutFilter runAsManager anonymousAuthenticationFilter logoutHandlers saltSource anonymousAuthenticationProvider logoutSuccessHandler securityContextHolderAwareRequestFilter authenticatedVoter objectDefinitionSource securityContextLogoutHandler authenticationDetailsSource passwordEncoder securityContextPersistenceFilter authenticationEntryPoint portMapper securityEventListener authenticationEventPublisher portResolver sessionAuthenticationStrategy authenticationFailureHandler postAuthenticationChecks springSecurityFilterChain authenticationManager preAuthenticationChecks tokenRepository authenticationProcessingFilter redirectStrategy userCache authenticationSuccessHandler rememberMeAuthenticationFilter userDetailsService authenticationTrustResolver rememberMeAuthenticationProvider webExpressionHandler authenticationUserDetailsService rememberMeServices webExpressionVoter daoAuthenticationProvider requestCache webInvocationPrivilegeEvaluator exceptionTranslationFilter roleHierarchy CONFIDENTIAL 33
Overriding Beans  Building the Spring ApplicationContext Core plugins Installed plugins resources.groovy CONFIDENTIAL 34
Overriding Beans  Gross oversimplifications: • The ApplicationContext is like a Map; the keys are bean names and the values are bean instances • Defining a bean with the same name as an earlier bean replaces the previous, just like Map.put(key, value) does CONFIDENTIAL 35
Overriding Beans  To change how a bean works: • Subclass the current class • Or subclass a similar class that's closer to what you need • Or implement the interface directly • Then register yours in grails-app/ conf/spring/resources.groovy CONFIDENTIAL 36
Overriding Beans - resources.groovy beans = { saltSource(com.foo.bar.MySaltSource) { // bean properties } userDetailsService(com.foo.bar.MyUserDetailsService) { // bean properties } passwordEncoder(com.foo.bar.MyPasswordEncoder) { // bean properties } } CONFIDENTIAL 37
Customizing Bean Properties CONFIDENTIAL 38
Customizing Bean Properties  In addition to declaring every bean, nearly all properties are configured from default values in the plugin's grails-app/conf/DefaultSecurityConfig.groovy  DO NOT EDIT DefaultSecurityConfig.groovy CONFIDENTIAL 39
Customizing Bean Properties  All properties can be overridden in app's Config.groovy • Be sure to add the grails.plugins.springsecurity prefix  Don't replace a bean if all you need to change is one or more properties CONFIDENTIAL 40
Customizing Bean Properties active dao.hideUserNotFoundExceptions registerLoggerListener successHandler.useReferer adh.ajaxErrorPage dao.reflectionSaltSourceProperty rejectIfNoRule switchUser.exitUserUrl adh.errorPage digest.createAuthenticatedToken rememberMe.alwaysRemember switchUser.switchFailureUrl ajaxHeader digest.key rememberMe.cookieName switchUser.switchUserUrl anon.key digest.nonceValiditySeconds rememberMe.key switchUser.targetUrl anon.userAttribute digest.passwordAlreadyEncoded rememberMe.parameter useBasicAuth apf.allowSessionCreation digest.realmName rememberMe.persistent useDigestAuth apf.continueChainBeforeSuccessfulAuthentication digest.useCleartextPasswords rememberMe.persistentToken.domainClassName useHttpSessionEventPublisher apf.filterProcessesUrl failureHandler.ajaxAuthFailUrl rememberMe.persistentToken.seriesLength userLookup.accountExpiredPropertyName apf.passwordParameter failureHandler.defaultFailureUrl rememberMe.persistentToken.tokenLength userLookup.accountLockedPropertyName apf.postOnly failureHandler.exceptionMappings rememberMe.tokenValiditySeconds userLookup.authoritiesPropertyName apf.usernameParameter failureHandler.useForward rememberMe.useSecureCookie userLookup.authorityJoinClassName atr.anonymousClass filterChain.stripQueryStringFromUrls requestCache.createSession userLookup.enabledPropertyName atr.rememberMeClass interceptUrlMap requestCache.onlyOnGet userLookup.passwordExpiredPropertyName auth.ajaxLoginFormUrl ipRestrictions requestMap.className userLookup.passwordPropertyName auth.forceHttps logout.afterLogoutUrl requestMap.configAttributeField userLookup.userDomainClassName auth.loginFormUrl logout.filterProcessesUrl requestMap.urlField userLookup.usernamePropertyName auth.useForward logout.handlerNames roleHierarchy useSecurityEventListener authenticationDetails.authClass password.algorithm secureChannel.definition useSessionFixationPrevention authority.className password.bcrypt.logrounds securityConfigType useSwitchUserFilter authority.nameField password.encodeHashAsBase64 sessionFixationPrevention.alwaysCreateSession useX509 basic.realmName portMapper.httpPort sessionFixationPrevention.migrate voterNames cacheUsers portMapper.httpsPort successHandler.ajaxSuccessUrl x509.checkForPrincipalChanges controllerAnnotations.lowercase providerManager.eraseCredentialsAfterAuthentication successHandler.alwaysUseDefault x509.continueFilterChainOnUnsuccessfulAuthentication controllerAnnotations.matcher providerNames successHandler.defaultTargetUrl x509.invalidateSessionOnPrincipalChange controllerAnnotations.staticRules redirectStrategy.contextRelative successHandler.targetUrlParameter x509.subjectDnRegex x509.throwExceptionWhenTokenRejected CONFIDENTIAL 41
Customizing Bean Properties grails-app/conf/Config.groovy: grails.plugins.springsecurity.userLookup.userDomainClassName = '…' grails.plugins.springsecurity.userLookup.authorityJoinClassName = '…' grails.plugins.springsecurity.authority.className = '…' grails.plugins.springsecurity.auth.loginFormUrl = '/log-in' grails.plugins.springsecurity.apf.filterProcessesUrl = '/authenticate' grails.plugins.springsecurity.logout.afterLogoutUrl = '/home' grails.plugins.springsecurity.userLookup.usernamePropertyName = 'email' CONFIDENTIAL 42
Customizing Bean Properties  Can also configure beans in BootStrap.groovy, e.g. to avoid redefining a whole bean in resources.groovy just to change one dependency: class BootStrap { def authenticationProcessingFilter def myAuthenticationFailureHandler def init = { servletContext -> authenticationProcessingFilter.authenticationFailureHandler = myAuthenticationFailureHandler } } CONFIDENTIAL 43
Custom UserDetailsService CONFIDENTIAL 44
Custom UserDetailsService  Very common customization  Documented in the plugin docs in section “11 Custom UserDetailsService” CONFIDENTIAL 45
Custom UserDetailsService  General workflow: • Create a custom o.s.s.core.userdetails.UserDetailsService (o.c.g.g.p.s.GrailsUserDetailsService) implementation • Directly implement the interface (best) • or extend org.codehaus.groovy.grails.plugins.springsecurity. GormUserDetailsService (ok, but usually cleaner to implement the interface) CONFIDENTIAL 46
Custom UserDetailsService  General workflow (cont.): • Create a custom implementation of o.s.s.core.userdetails.UserDetails • Extend org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser • or extend o.s.s.core.userdetails.User • or directly implement the interface CONFIDENTIAL 47
Custom UserDetailsService  General workflow (cont.): • Register the bean override in grails-app/conf/spring/resources.groovy: import com.mycompany.myapp.MyUserDetailsService beans = { userDetailsService(MyUserDetailsService) } CONFIDENTIAL 48
Custom AuthenticationProvider CONFIDENTIAL 49
Custom AuthenticationProvider  General workflow: • Create a custom o.s.s.authentication.AuthenticationProvider implementation • Extend one that's similar, e.g. o.s.s.authentication.dao.DaoAuthenticationProvider • or directly implement the interface CONFIDENTIAL 50
Custom AuthenticationProvider  General workflow (cont.): • You'll probably want a custom o.s.s.core.Authentication • Extend an existing implementation, e.g. o.s.s.authentication.UsernamePasswordAuthenticationToken • or directly implement the interface CONFIDENTIAL 51
Custom AuthenticationProvider  General workflow (cont.): • If you're using a custom Authentication, you'll need a filter to create it • Create a custom javax.servlet.Filter implementation • Best to extend org.springframework.web.filter.GenericFilterBean • or one that's similar, e.g. o.s.s.web.authentication.UsernamePasswordAuthenticationFilter • Or directly implement the interface CONFIDENTIAL 52
Custom AuthenticationProvider  Registering the custom classes • If you're replacing functionality, register bean overrides in resources.groovy • If you're adding an alternate authentication option (e.g. for only some URLs) • Register the beans in resources.groovy • Register the provider as described in section “10 Authentication Providers” of the docs • Register the filter in its position as described in section “16 Filters” of the docs CONFIDENTIAL 53
Custom Post-logout Behavior CONFIDENTIAL 54
Custom Post-logout Behavior  Recall that logout is processed by MutableLogoutFilter after request for /j_spring_security_logout  handler.logout(request, response, auth) is called for each LogoutHandler  then redirect to post-logout url (by default ”/”) CONFIDENTIAL 55
Custom Post-logout Behavior  The default LogoutHandler implementations are • o.s.s.web.authentication.rememberme.TokenBasedRememberMeServices • Deletes the remember-me cookie • o.s.s.web.authentication.logout.SecurityContextLogoutHandler • Invalidates the HttpSession • Removes the SecurityContext from the SecurityContextHolder CONFIDENTIAL 56
Custom Post-logout Behavior  Redirect is triggered by • logoutSuccessHandler.onLogoutSuccess(request, response, auth) • LogoutSuccessHandler is an instance of o.s.s.web.authentication.logout.SimpleUrlLogoutSuccessHandler CONFIDENTIAL 57
Custom Post-logout Behavior  To add logic to dynamically determine redirect url: • Subclass SimpleUrlLogoutSuccessHandler • Since the Authentication isn't available in determineTargetUrl, override onLogoutSuccess and set it in a ThreadLocal CONFIDENTIAL 58
Custom Post-logout Behavior  To add logic to dynamically determine redirect url (cont.): private static final ThreadLocal<Authentication> AUTH_HOLDER = new ThreadLocal<Authentication>() … void onLogoutSuccess(...) throws ... { AUTH_HOLDER.set authentication try { super.handle(request, response, authentication) } finally { AUTH_HOLDER.remove() } } CONFIDENTIAL 59
Custom Post-logout Behavior  To add logic to dynamically determine redirect url (cont.): • Override determineTargetUrl protected String determineTargetUrl(...) { Authentication auth = AUTH_HOLDER.get() String url = super.determineTargetUrl(request, response) if (auth instanceof OrganizationAuthentication) { OrganizationAuthentication authentication = auth url = ... } url } CONFIDENTIAL 60
Custom Post-logout Behavior  To add logic to dynamically determine redirect url (cont.): • Redefine the logoutSuccessHandler bean in resources.groovy import com.mycompany.myapp.CustomLogoutSuccessHandler import o.c.g.g.plugins.springsecurity.SpringSecurityUtils beans = { def conf = SpringSecurityUtils.securityConfig logoutSuccessHandler(CustomLogoutSuccessHandler) { redirectStrategy = ref('redirectStrategy') defaultTargetUrl = conf.logout.afterLogoutUrl } } CONFIDENTIAL 61
Customization Overview CONFIDENTIAL 62
Customization Overview  Subclass or replace filters in the chain: • SecurityContextPersistenceFilter • MutableLogoutFilter • RequestHolderAuthenticationFilter (UsernamePasswordAuthenticationFilter) • SecurityContextHolderAwareRequestFilter • RememberMeAuthenticationFilter • AnonymousAuthenticationFilter • ExceptionTranslationFilter • FilterSecurityInterceptor CONFIDENTIAL 63
Customization Overview (cont)  Remove filter(s) from the chain • Not recommended  Add filter(s) to the chain  Add/remove/replace LogoutHandler(s)  Add/remove/replace AccessDecisionVoter(s)  Add/remove/replace AuthenticationProvider(s) CONFIDENTIAL 64
Customization Overview (cont)  Customize a filter or AuthenticationProvider's dependency • e.g. custom UserDetailsService  Don't write code if you can customize properties or BootStrap.groovy  Read the Spring Security documentation • Print the PDF and read it on your commute  Ask for help on the Grails User mailing list CONFIDENTIAL 65
See http://burtbeckwith.com/blog/?p=1090 for a blog post on the previous talk in London CONFIDENTIAL 66
Thank you CONFIDENTIAL 67

Hacking the Grails Spring Security Plugins

  • 1.
    Hacking the Grails Spring  Security Plugins Burt Beckwith SpringSource
  • 2.
  • 3.
  • 4.
    web.xml  Spring Securityis implemented as a filter chain, so the <filter-mapping> elements are the important ones: • charEncodingFilter • hiddenHttpMethod • grailsWebRequest • springSecurityFilterChain • reloadFilter • sitemesh • urlMapping CONFIDENTIAL 4
  • 5.
    web.xml  Spring Securityis implemented as a filter chain, so the <filter-mapping> elements are the important ones: • charEncodingFilter • hiddenHttpMethod • grailsWebRequest • springSecurityFilterChain • reloadFilter • sitemesh • urlMapping CONFIDENTIAL 5
  • 6.
    web.xml - charEncodingFilter org.springframework.web.filter.DelegatingFilterProxy  Uses the "characterEncodingFilter" bean (org.springframework.web.filter.CharacterEncodingFilter) defined in applicationContext.xml (the “parent” context)  request.setCharacterEncoding("utf-8");  response.setCharacterEncoding("utf-8"); CONFIDENTIAL 6
  • 7.
    web.xml - hiddenHttpMethod org.codehaus.groovy.grails.web.filters.HiddenHttpMethodFilter String httpMethod = getHttpMethodOverride(request); filterChain.doFilter( new HttpMethodRequestWrapper(httpMethod, request), response);  <g:form controller="book" method="DELETE">  See section “13.1 REST” in the docs CONFIDENTIAL 7
  • 8.
    web.xml - grailsWebRequest org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter LocaleContextHolder.setLocale(request.getLocale()); GrailsWebRequest webRequest = new GrailsWebRequest( request, response, getServletContext()); configureParameterCreationListeners(webRequest); try { WebUtils.storeGrailsWebRequest(webRequest); // Set the flash scope instance to its next state FlashScope fs = webRequest.getAttributes().getFlashScope(request); fs.next(); filterChain.doFilter(request, response); } finally { webRequest.requestCompleted(); WebUtils.clearGrailsWebRequest(); LocaleContextHolder.setLocale(null); } CONFIDENTIAL 8
  • 9.
    web.xml - springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy  Uses the "springSecurityFilterChain" bean defined by the plugin (org.springframework.security.web.FilterChainProxy) CONFIDENTIAL 9
  • 10.
    web.xml - springSecurityFilterChain Typical filter beans: • securityContextPersistenceFilter • logoutFilter • authenticationProcessingFilter • securityContextHolderAwareRequestFilter • rememberMeAuthenticationFilter • anonymousAuthenticationFilter • exceptionTranslationFilter • filterInvocationInterceptor CONFIDENTIAL 10
  • 11.
    web.xml - reloadFilter org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter  No longer used in 2.0+  “Copies resources from the source on content change and manages reloading if necessary.” GrailsApplication application = (GrailsApplication)context.getBean( GrailsApplication.APPLICATION_ID); if (!application.isInitialised()) { application.rebuild(); GrailsRuntimeConfigurator config = new GrailsRuntimeConfigurator( application, parent); config.reconfigure(context, getServletContext(), true); } CONFIDENTIAL 11
  • 12.
    web.xml - sitemesh org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter  Subclass of com.opensymphony.sitemesh.webapp.SiteMeshFilter  Renders Sitemesh templates/layouts CONFIDENTIAL 12
  • 13.
    web.xml - urlMapping org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter  Matches requested url to the correct controller/action/view/error code  Based on mappings in grails-app/conf/UrlMappings.groovy CONFIDENTIAL 13
  • 14.
  • 15.
    springSecurityFilterChain - securityContextPersistenceFilter org.springframework.security.web.context.SecurityContextPersistenceFilter  Retrieves the SecurityContext from the HTTP session (under the ”SPRING_SECURITY_CONTEXT” key) or creates a new empty one  Stores the context in the holder: SecurityContextHolder.setContext()  Removes the context after request CONFIDENTIAL 15
  • 16.
    springSecurityFilterChain - logoutFilter org.codehaus.groovy.grails.plugins.springsecurity.MutableLogoutFilter  If uri is ”/j_spring_security_logout” calls handler.logout(request, response, auth) for each o.s.s.web.authentication.logout.LogoutHandler and redirects to post- logout url (by default ”/”) CONFIDENTIAL 16
  • 17.
    springSecurityFilterChain - authenticationProcessingFilter org.codehaus.groovy.grails.plugins.springsecurity.RequestHolderAuthenticationFilter (extends o.s.s.web.authentication.UsernamePasswordAuthenticationFilter)  Sets the request and response in the SecurityRequestHolder ThreadLocal fields (custom plugin functionality)  If uri is ”/j_spring_security_check” calls attemptAuthentication() CONFIDENTIAL 17
  • 18.
    springSecurityFilterChain - securityContextHolderAwareRequestFilter o.s.s.web.servletapi.SecurityContextHolderAwareRequestFilter  Configures a request wrapper which implements the servlet API security methods (getRemoteUser, getUserPrincipal, isUserInRole) chain.doFilter(new SecurityContextHolderAwareRequestWrapper( request, rolePrefix), response); CONFIDENTIAL 18
  • 19.
    springSecurityFilterChain - rememberMeAuthenticationFilter o.s.s.web.authentication.rememberme.RememberMeAuthenticationFilter  If not logged in, attempt to login from the remember-me cookie CONFIDENTIAL 19
  • 20.
    springSecurityFilterChain - anonymousAuthenticationFilter o.s.s.web.authentication.AnonymousAuthenticationFilter  If not logged in creates an AnonymousAuthenticationToken and registers it as the Authentication in the SecurityContext https://secure.flickr.com/photos/gaelx/5445598436/ CONFIDENTIAL 20
  • 21.
    springSecurityFilterChain - exceptionTranslationFilter o.s.s.web.access.ExceptionTranslationFilter  Handles AccessDeniedException and AuthenticationException  For AuthenticationException will invoke the authenticationEntryPoint  For AccessDeniedException invoke the authenticationEntryPoint if anonymous, otherwise delegate to o.s.s.web.access.AccessDeniedHandler CONFIDENTIAL 21
  • 22.
    springSecurityFilterChain - filterInvocationInterceptor o.s.s.web.access.intercept.FilterSecurityInterceptor  Determines the o.s.s.access.SecurityConfig collection for the request from FilterInvocationSecurityMetadataSource (AnnotationFilterInvocationDefinition, InterceptUrlMapFilterInvocationDefinition, or RequestmapFilterInvocationDefinition) CONFIDENTIAL 22
  • 23.
    springSecurityFilterChain - filterInvocationInterceptor Delegates to an AccessDecisionManager (org.codehaus.groovy.grails.plugins.springsecurity. AuthenticatedVetoableDecisionManager) to call each registered o.s.s.access.AccessDecisionVoter CONFIDENTIAL 23
  • 24.
    springSecurityFilterChain - filterInvocationInterceptor o.s.s.access.vote.AuthenticatedVoter works with “IS_AUTHENTICATED_FULLY”, “IS_AUTHENTICATED_REMEMBERED”, and “IS_AUTHENTICATED_ANONYMOUSLY” CONFIDENTIAL 24
  • 25.
    springSecurityFilterChain - filterInvocationInterceptor o.s.s.access.vote.RoleHierarchyVoter finds the GrantedAuthority instances from the Authentication, checks for matches with required roles CONFIDENTIAL 25
  • 26.
    springSecurityFilterChain - filterInvocationInterceptor org.codehaus.groovy.grails.plugins.springsecurity. WebExpressionVoter looks for a WebExpressionConfigAttribute and evaluates its expression if found CONFIDENTIAL 26
  • 27.
    springSecurityFilterChain - filterInvocationInterceptor Common built-in expressions Expression Description hasRole([role]) Returns true if the current principal has the specified role. hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings) principal Allows direct access to the principal object representing the current user authentication Allows direct access to the current Authentication object obtained from the SecurityContext permitAll Always evaluates to true denyAll Always evaluates to false isAnonymous() Returns true if the current principal is an anonymous user isRememberMe() Returns true if the current principal is a remember-me user isAuthenticated() Returns true if the user is not anonymous isFullyAuthenticated() Returns true if the user is not an anonymous or a remember-me user CONFIDENTIAL 27
  • 28.
    springSecurityFilterChain - filterInvocationInterceptor if (denyCount > 0) { throw new AccessDeniedException("Access is denied"); }  this is caught by the exceptionTranslationFilter  If the Authentication is anonymous, stores a SavedRequest in the HTTP session and calls authenticationEntryPoint.commence( request, response, reason)  The authenticationEntryPoint is an org.codehaus.groovy.grails.plugins.springsecurity. AjaxAwareAuthenticationEntryPoint  This issues a redirect to the login page, by default /login/auth CONFIDENTIAL 28
  • 29.
    Customizing the Plugin CONFIDENTIAL 29
  • 30.
    Customizing the Plugin Traditional Spring Security uses XML with namespaced beans: <beans:beans xmlns="http://www.springframework.org/schema/security" ...> <http auto-config="true"> <intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" /> </authentication-provider> </authentication-manager> </beans:beans> CONFIDENTIAL 30
  • 31.
    Customizing the Plugin Simple; not always clear how to customize; many options aren't exposed  In contrast, the plugin explicitly defines every bean  See SpringSecurityCoreGrailsPlugin.groovy for the details CONFIDENTIAL 31
  • 32.
    Overriding Beans CONFIDENTIAL 32
  • 33.
    Overriding Beans accessDecisionManager filterInvocationInterceptor roleVoter accessDeniedHandler logoutFilter runAsManager anonymousAuthenticationFilter logoutHandlers saltSource anonymousAuthenticationProvider logoutSuccessHandler securityContextHolderAwareRequestFilter authenticatedVoter objectDefinitionSource securityContextLogoutHandler authenticationDetailsSource passwordEncoder securityContextPersistenceFilter authenticationEntryPoint portMapper securityEventListener authenticationEventPublisher portResolver sessionAuthenticationStrategy authenticationFailureHandler postAuthenticationChecks springSecurityFilterChain authenticationManager preAuthenticationChecks tokenRepository authenticationProcessingFilter redirectStrategy userCache authenticationSuccessHandler rememberMeAuthenticationFilter userDetailsService authenticationTrustResolver rememberMeAuthenticationProvider webExpressionHandler authenticationUserDetailsService rememberMeServices webExpressionVoter daoAuthenticationProvider requestCache webInvocationPrivilegeEvaluator exceptionTranslationFilter roleHierarchy CONFIDENTIAL 33
  • 34.
    Overriding Beans  Buildingthe Spring ApplicationContext Core plugins Installed plugins resources.groovy CONFIDENTIAL 34
  • 35.
    Overriding Beans  Grossoversimplifications: • The ApplicationContext is like a Map; the keys are bean names and the values are bean instances • Defining a bean with the same name as an earlier bean replaces the previous, just like Map.put(key, value) does CONFIDENTIAL 35
  • 36.
    Overriding Beans  Tochange how a bean works: • Subclass the current class • Or subclass a similar class that's closer to what you need • Or implement the interface directly • Then register yours in grails-app/ conf/spring/resources.groovy CONFIDENTIAL 36
  • 37.
    Overriding Beans -resources.groovy beans = { saltSource(com.foo.bar.MySaltSource) { // bean properties } userDetailsService(com.foo.bar.MyUserDetailsService) { // bean properties } passwordEncoder(com.foo.bar.MyPasswordEncoder) { // bean properties } } CONFIDENTIAL 37
  • 38.
  • 39.
    Customizing Bean Properties In addition to declaring every bean, nearly all properties are configured from default values in the plugin's grails-app/conf/DefaultSecurityConfig.groovy  DO NOT EDIT DefaultSecurityConfig.groovy CONFIDENTIAL 39
  • 40.
    Customizing Bean Properties All properties can be overridden in app's Config.groovy • Be sure to add the grails.plugins.springsecurity prefix  Don't replace a bean if all you need to change is one or more properties CONFIDENTIAL 40
  • 41.
    Customizing Bean Properties active dao.hideUserNotFoundExceptions registerLoggerListener successHandler.useReferer adh.ajaxErrorPage dao.reflectionSaltSourceProperty rejectIfNoRule switchUser.exitUserUrl adh.errorPage digest.createAuthenticatedToken rememberMe.alwaysRemember switchUser.switchFailureUrl ajaxHeader digest.key rememberMe.cookieName switchUser.switchUserUrl anon.key digest.nonceValiditySeconds rememberMe.key switchUser.targetUrl anon.userAttribute digest.passwordAlreadyEncoded rememberMe.parameter useBasicAuth apf.allowSessionCreation digest.realmName rememberMe.persistent useDigestAuth apf.continueChainBeforeSuccessfulAuthentication digest.useCleartextPasswords rememberMe.persistentToken.domainClassName useHttpSessionEventPublisher apf.filterProcessesUrl failureHandler.ajaxAuthFailUrl rememberMe.persistentToken.seriesLength userLookup.accountExpiredPropertyName apf.passwordParameter failureHandler.defaultFailureUrl rememberMe.persistentToken.tokenLength userLookup.accountLockedPropertyName apf.postOnly failureHandler.exceptionMappings rememberMe.tokenValiditySeconds userLookup.authoritiesPropertyName apf.usernameParameter failureHandler.useForward rememberMe.useSecureCookie userLookup.authorityJoinClassName atr.anonymousClass filterChain.stripQueryStringFromUrls requestCache.createSession userLookup.enabledPropertyName atr.rememberMeClass interceptUrlMap requestCache.onlyOnGet userLookup.passwordExpiredPropertyName auth.ajaxLoginFormUrl ipRestrictions requestMap.className userLookup.passwordPropertyName auth.forceHttps logout.afterLogoutUrl requestMap.configAttributeField userLookup.userDomainClassName auth.loginFormUrl logout.filterProcessesUrl requestMap.urlField userLookup.usernamePropertyName auth.useForward logout.handlerNames roleHierarchy useSecurityEventListener authenticationDetails.authClass password.algorithm secureChannel.definition useSessionFixationPrevention authority.className password.bcrypt.logrounds securityConfigType useSwitchUserFilter authority.nameField password.encodeHashAsBase64 sessionFixationPrevention.alwaysCreateSession useX509 basic.realmName portMapper.httpPort sessionFixationPrevention.migrate voterNames cacheUsers portMapper.httpsPort successHandler.ajaxSuccessUrl x509.checkForPrincipalChanges controllerAnnotations.lowercase providerManager.eraseCredentialsAfterAuthentication successHandler.alwaysUseDefault x509.continueFilterChainOnUnsuccessfulAuthentication controllerAnnotations.matcher providerNames successHandler.defaultTargetUrl x509.invalidateSessionOnPrincipalChange controllerAnnotations.staticRules redirectStrategy.contextRelative successHandler.targetUrlParameter x509.subjectDnRegex x509.throwExceptionWhenTokenRejected CONFIDENTIAL 41
  • 42.
    Customizing Bean Properties grails-app/conf/Config.groovy: grails.plugins.springsecurity.userLookup.userDomainClassName = '…' grails.plugins.springsecurity.userLookup.authorityJoinClassName = '…' grails.plugins.springsecurity.authority.className = '…' grails.plugins.springsecurity.auth.loginFormUrl = '/log-in' grails.plugins.springsecurity.apf.filterProcessesUrl = '/authenticate' grails.plugins.springsecurity.logout.afterLogoutUrl = '/home' grails.plugins.springsecurity.userLookup.usernamePropertyName = 'email' CONFIDENTIAL 42
  • 43.
    Customizing Bean Properties Can also configure beans in BootStrap.groovy, e.g. to avoid redefining a whole bean in resources.groovy just to change one dependency: class BootStrap { def authenticationProcessingFilter def myAuthenticationFailureHandler def init = { servletContext -> authenticationProcessingFilter.authenticationFailureHandler = myAuthenticationFailureHandler } } CONFIDENTIAL 43
  • 44.
  • 45.
    Custom UserDetailsService  Verycommon customization  Documented in the plugin docs in section “11 Custom UserDetailsService” CONFIDENTIAL 45
  • 46.
    Custom UserDetailsService  Generalworkflow: • Create a custom o.s.s.core.userdetails.UserDetailsService (o.c.g.g.p.s.GrailsUserDetailsService) implementation • Directly implement the interface (best) • or extend org.codehaus.groovy.grails.plugins.springsecurity. GormUserDetailsService (ok, but usually cleaner to implement the interface) CONFIDENTIAL 46
  • 47.
    Custom UserDetailsService  Generalworkflow (cont.): • Create a custom implementation of o.s.s.core.userdetails.UserDetails • Extend org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser • or extend o.s.s.core.userdetails.User • or directly implement the interface CONFIDENTIAL 47
  • 48.
    Custom UserDetailsService  Generalworkflow (cont.): • Register the bean override in grails-app/conf/spring/resources.groovy: import com.mycompany.myapp.MyUserDetailsService beans = { userDetailsService(MyUserDetailsService) } CONFIDENTIAL 48
  • 49.
  • 50.
    Custom AuthenticationProvider  Generalworkflow: • Create a custom o.s.s.authentication.AuthenticationProvider implementation • Extend one that's similar, e.g. o.s.s.authentication.dao.DaoAuthenticationProvider • or directly implement the interface CONFIDENTIAL 50
  • 51.
    Custom AuthenticationProvider  Generalworkflow (cont.): • You'll probably want a custom o.s.s.core.Authentication • Extend an existing implementation, e.g. o.s.s.authentication.UsernamePasswordAuthenticationToken • or directly implement the interface CONFIDENTIAL 51
  • 52.
    Custom AuthenticationProvider  Generalworkflow (cont.): • If you're using a custom Authentication, you'll need a filter to create it • Create a custom javax.servlet.Filter implementation • Best to extend org.springframework.web.filter.GenericFilterBean • or one that's similar, e.g. o.s.s.web.authentication.UsernamePasswordAuthenticationFilter • Or directly implement the interface CONFIDENTIAL 52
  • 53.
    Custom AuthenticationProvider  Registeringthe custom classes • If you're replacing functionality, register bean overrides in resources.groovy • If you're adding an alternate authentication option (e.g. for only some URLs) • Register the beans in resources.groovy • Register the provider as described in section “10 Authentication Providers” of the docs • Register the filter in its position as described in section “16 Filters” of the docs CONFIDENTIAL 53
  • 54.
  • 55.
    Custom Post-logout Behavior Recall that logout is processed by MutableLogoutFilter after request for /j_spring_security_logout  handler.logout(request, response, auth) is called for each LogoutHandler  then redirect to post-logout url (by default ”/”) CONFIDENTIAL 55
  • 56.
    Custom Post-logout Behavior The default LogoutHandler implementations are • o.s.s.web.authentication.rememberme.TokenBasedRememberMeServices • Deletes the remember-me cookie • o.s.s.web.authentication.logout.SecurityContextLogoutHandler • Invalidates the HttpSession • Removes the SecurityContext from the SecurityContextHolder CONFIDENTIAL 56
  • 57.
    Custom Post-logout Behavior Redirect is triggered by • logoutSuccessHandler.onLogoutSuccess(request, response, auth) • LogoutSuccessHandler is an instance of o.s.s.web.authentication.logout.SimpleUrlLogoutSuccessHandler CONFIDENTIAL 57
  • 58.
    Custom Post-logout Behavior To add logic to dynamically determine redirect url: • Subclass SimpleUrlLogoutSuccessHandler • Since the Authentication isn't available in determineTargetUrl, override onLogoutSuccess and set it in a ThreadLocal CONFIDENTIAL 58
  • 59.
    Custom Post-logout Behavior To add logic to dynamically determine redirect url (cont.): private static final ThreadLocal<Authentication> AUTH_HOLDER = new ThreadLocal<Authentication>() … void onLogoutSuccess(...) throws ... { AUTH_HOLDER.set authentication try { super.handle(request, response, authentication) } finally { AUTH_HOLDER.remove() } } CONFIDENTIAL 59
  • 60.
    Custom Post-logout Behavior To add logic to dynamically determine redirect url (cont.): • Override determineTargetUrl protected String determineTargetUrl(...) { Authentication auth = AUTH_HOLDER.get() String url = super.determineTargetUrl(request, response) if (auth instanceof OrganizationAuthentication) { OrganizationAuthentication authentication = auth url = ... } url } CONFIDENTIAL 60
  • 61.
    Custom Post-logout Behavior To add logic to dynamically determine redirect url (cont.): • Redefine the logoutSuccessHandler bean in resources.groovy import com.mycompany.myapp.CustomLogoutSuccessHandler import o.c.g.g.plugins.springsecurity.SpringSecurityUtils beans = { def conf = SpringSecurityUtils.securityConfig logoutSuccessHandler(CustomLogoutSuccessHandler) { redirectStrategy = ref('redirectStrategy') defaultTargetUrl = conf.logout.afterLogoutUrl } } CONFIDENTIAL 61
  • 62.
    Customization Overview CONFIDENTIAL 62
  • 63.
    Customization Overview  Subclassor replace filters in the chain: • SecurityContextPersistenceFilter • MutableLogoutFilter • RequestHolderAuthenticationFilter (UsernamePasswordAuthenticationFilter) • SecurityContextHolderAwareRequestFilter • RememberMeAuthenticationFilter • AnonymousAuthenticationFilter • ExceptionTranslationFilter • FilterSecurityInterceptor CONFIDENTIAL 63
  • 64.
    Customization Overview (cont) Remove filter(s) from the chain • Not recommended  Add filter(s) to the chain  Add/remove/replace LogoutHandler(s)  Add/remove/replace AccessDecisionVoter(s)  Add/remove/replace AuthenticationProvider(s) CONFIDENTIAL 64
  • 65.
    Customization Overview (cont) Customize a filter or AuthenticationProvider's dependency • e.g. custom UserDetailsService  Don't write code if you can customize properties or BootStrap.groovy  Read the Spring Security documentation • Print the PDF and read it on your commute  Ask for help on the Grails User mailing list CONFIDENTIAL 65
  • 66.
    See http://burtbeckwith.com/blog/?p=1090 for a blog post on the previous talk in London CONFIDENTIAL 66
  • 67.
    Thank you CONFIDENTIAL 67