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 onAuthorizationEvent
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.
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
}
}