Search code examples
grailsspring-securitygrails-2.0grails-spring-security

How to make Grails's Spring Security Core render a different page depending on the reason why access was denied


I'm running a Grails Application using Spring Security Core Plugin.

When an already logged in user tries to access a page without without access to it, the same action configured as 403 in UrlMappings.groovy is always called.

I've been struggling to make my application render a different page depending on the cause of the access denial. For example: If IS_AUTHENTICATED_FULLY is required I want to redirect the user to a form where he can reauthenticate. If a specific role is required but not present I want to redirect the user to a page where he can request this role. And so on...

Does anyone knows how to archive that?

============================== UPDATE ==================================

I tried to solve the problem via the onAuthorizationEven‌​t callback described on the docs. Unfortunately the event parameter is always identical no matter the rule that triggered it.

At least I have access to the URI which was denied access from there. Is there a way to get the security rules from a URI so I can compare to the current user's roles and status and find out what is missing? That maybe would solve the problem.


Solution

  • After a long research through the internet, finally got it to work with some ideas from @yariash 's response and this post:

    import org.springframework.context.ApplicationListener;
    import org.springframework.security.access.event.AuthorizationFailureEvent
    import org.springframework.security.authentication.RememberMeAuthenticationToken;
    
    class AuthorizationFailureEventListener
            implements ApplicationListener<AuthorizationFailureEvent> {
    
        @Override
        public void onApplicationEvent(AuthorizationFailureEvent e) {
            def attributes = e.getConfigAttributes()
    
            if(!attributes) {
                return
            }
    
            def authentication = e.getAuthentication()
            def requiredAuthorities = attributes?.collect { it.getAttribute() }
            def userAuthorities = authentication.getAuthorities().collect { it.getAuthority() }
            def missingAuthorities = requiredAuthorities - userAuthorities
    
            if(requiredAuthorities.contains('IS_AUTHENTICATED_FULLY') &&
                    !(authentication instanceof RememberMeAuthenticationToken)) {
                requiredAuthorities.remove('IS_AUTHENTICATED_FULLY')
            }
    
            e.getSource().getRequest().setAttribute("MISSING_AUTHORITIES", missingAuthorities);
        }
    
    }
    

    Then include this listenner as a bean:

    beans = {
        //...
        authorizationFailureEventListener(AuthorizationFailureEventListener) { bean ->
            bean.autowire = "byName"
        }
        //...
    }
    

    Finally in my error controller:

    static mappings = {
        //....
        "403"(controller:'error', action:'error403')
        //......
    }
    
    class ErrorController {  
        def error403() {
            def missingAuthorities = request.getAttribute("MISSING_AUTHORITIES")
            // Render the right view based on the missing authorities
        }
    }