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.

2

There are 2 answers

1
ylima On BEST ANSWER

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
    }
}
1
Krzysztof Atłasik On

Maybe create controller and access exception there?

static mappings = {
    //....
   "403"(controller:'error', action:'error403')
   //......
}


class ErrorController {  
   def error403() {
       def message = request.exception?.cause?.message; //access message and render right view
   }
}