Posted by: johnnywey | October 29, 2009

Grails Acegi Plugin and Securing Multiple Resources using Basic Authentication

Grails is pretty awesome. Not only does it use Groovy as its main language, but it also provides nice DSL access to Spring settings.

Securing a website is something that can often be fairly complex and easy to get wrong. Grails provides a plugin for the state-of-the-art security model Spring Security (also called Acegi) (plugin can be found here).

Everything was all well and good until I tried to use two different security models simultaneously: a form-based login for human beings and an http basic authentication login for machines consuming an API.

This turned out to be pretty hard. The plugin requires setting the following in the resources.groovy file to work with basic authentication at all:

beans = {
  authenticationEntryPoint(org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint) {
    realmName = 'Grails Realm'
  }
}

(The plugin is supposed to automatically do this by setting the basicProcessingFilter = true flag in the SecurityConfigure.groovy file, but there is a bug as of version 0.5.2 which is, as of now, the latest production release.) This worked for basic authentication, but would no longer redirect users to the form-based login.

Back to the drawing board.

What I discovered was that I could change between using the form-based login or the http headers authentication but not use them both. (I should clarify this by saying that I could shove the headers into the request and it would work, but a challenge response of 401 was not being sent back to the client which technically breaks the http authentication spec.) I found this thread (with contributions from the author of the Acegi plugin itself) but, while it pointed me in a nice direction, it didn’t answer the question fully. What I really needed was a way to use the URL to decide how to authenticate.

I finally discovered that the ExceptionTranslationFilter was not unique for the URLs I was using. In short, if the URL looked like: http://www.test.com/application/api/user/show/1, I wanted to use basic authentication and if the URL was http://www.test.com/application/user/show/1, I wanted to use the form-based authentication. The redirect is controlled by the ExceptionTranslationFilter and there was only one (so only one model would work at a time).

All I ended up having to do is create my own ExceptionTranslationFilter and wire it to the API URL. The final code in resources.groovy looks as follows:

import org.springframework.security.util.FilterChainProxy
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsAccessDeniedHandlerImpl
import org.springframework.security.ui.ExceptionTranslationFilter

// Place your Spring DSL code here
beans = {
  basicAuthenticationEntryPoint(org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint) {
    realmName = 'Grails Realm'
  }

  basicExceptionTranslationFilter(ExceptionTranslationFilter) {
    authenticationEntryPoint = ref('basicAuthenticationEntryPoint')
    accessDeniedHandler = ref('accessDeniedHandler')
    portResolver = ref('portResolver')
  }

  springSecurityFilterChain(FilterChainProxy) {
    filterInvocationDefinitionSource = """
         CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
         PATTERN_TYPE_APACHE_ANT
         /xml*/**=authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
         /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
         """
  }
}

Now, visiting any URL that prefixed by a /xml/ will be a call to the basic authentication headers and every other prefix will be a standard form-based login.

Since I shaved a serious yak figuring this out, I hope it helps someone!


Responses

  1. I have suffered lots of pain due to the plugin and I will soon remove it to setup Spring Security myself. It is pretty easy to do and one is finally in full control.


Leave a response

Your response:

Categories