0

I'm using Grails 2.2.3 and the Spring Security ACL plugin 1.1.1, and I'd like to have a URL that is open to the public and the service layer using the @PostAuthorize annotation secures the resource. We're doing it this way because to determine whether a user has access to a particular object we need to look at the object first.

What I'd like to be able to do is in the controller layer catch the AccessDeniedException, then have the browser ask for credentials and try again. I've tried the naive approach of setting the response status to 401 and redirecting back to itself to try again. The problem I ran into is that the browser never asked for credentials.

To put this into code what I'd like to do is in the controller layer:

@Secured(['IS_AUTHENTICATED_ANONYMOUSLY'])
def controllerAction() {
   try {
      someService.action()
   } catch (AccessDeniedException ade) {
      // if logged in show FORBIDDEN, if not ask for credentials and try again
   }
}

And the service layer would simply have:

@PostAuthorize("""returnObject.availability == 'ALL'""")
def action() {
   PersistedObject.findById(1)
}

Thanks for any help!

Patrick McDaniel
  • 1,073
  • 4
  • 11
  • 28
  • Can you post also the code where you set the response status to 401 and redirect back? Anyway, it could be maybe possible in your case to grant access to objects available to all via ACL and then you do not need to load object first to check if a user is permitted to work with such an object. Simply grant e.g. READ access to a role assigned to all users (e.g. ROLE_USER). – pgiecek Jan 11 '14 at 18:34
  • That's kind of what we already do. The redirect was simply setting response.status = 401, redirect(self). The problem there is the redirect is setting the status to 302 which doesn't ask for login. The problem isn't accessing these objects anonymously the problem is accessing the objects anonymously, then if that fails asking for username/password. – Patrick McDaniel Jan 13 '14 at 13:52
  • I am not sure if I understand your flow correctly. Are you sending 401 or 302 HTTP response? When you send 302 HTTP response then a user usually do not notice that since an User Agent (e.g. web browser) automatically redirects to a URL stated in Location header. Anyway, AccessDeniedException does not mean that a user is not authenticated but rather that the user is not authorised to access some resource. Correct response should be HTTP 403. Probably the easiest way would be to implement custom `org.springframework.security.web.access.AccessDeniedHandler` that will return whatever you need. – pgiecek Jan 13 '14 at 14:36
  • I apologize for my previous comment as I seem to have confused you. I understand AccessDeniedException means that the user is not authorized, but in my case the "user" may be anonymous. In that case I'd like to send 401 so the browser asks the anonymous user to authenticate and try again. If the user is not anonymous then send a 403. I don't think an AccessDeniedHandler is what I want as that will apply globally to all endpoints where I just want this one endpoint to behave this way. – Patrick McDaniel Jan 13 '14 at 18:14
  • Anyway, consider to handle `AccessDeniedException` in custom handler as mentioned above. Then you have only component dedicated to deal with such situations instead of several blocks of codes spread across you codebase. In the handler you can send different response based on request URL. – pgiecek Jan 13 '14 at 21:54

1 Answers1

0

I ended up solving the problem, and it turns out I was missing a header that I needed to send along with the 401 status code.

To correct what I have above what you want to do is:

@Secured(['IS_AUTHENTICATED_ANONYMOUSLY'])
def controllerAction() {
   try {
      someService.action()
   } catch (AccessDeniedException ade) {
      if (!springSecurityService.isLoggedIn()) {
         response.setHeader 'WWW-Authenticate', 'Basic realm="Grails Realm"' // the missing line
         response.sendError HttpServletResponse.SC_UNAUTHORIZED 
   }
}

The "redirect" I was looking for happens automatically within the browser after credentials are submitted. I was mistaken about needing something specific for the redirect.

Patrick McDaniel
  • 1,073
  • 4
  • 11
  • 28