Groovy

Grails Custom AuthenticationProvider

In order to tighten up security in our new Grails app I went about implementing the Spring Security Plugin. Getting it up and running with a standard username/password scenario was simple, as that is all wired up automagically by the plugin. That solved half of my problem, but we also need to support authentication with SAML, and there were no clear examples of how to do that. I’d like to share what I built in case anyone has a similar requirement. I won’t focus on the SAML specifics, but rather on how to build any custom authentication provider in grails.
You can map a URL to a filter by extending AbstractAuthenticationProcessingFilter and registering it with Spring. Then you can provide that URL for custom authentication. In my case it looked something like this:
class SamlAuthenticationFilter extends AbstractAuthenticationProcessingFilter  {

    public SamlAuthenticationFilter() {
        super("/somecustomauth")
    }

    @Override
    Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod())
        }

        String accessToken = request.getParameter("sometoken")
        return this.getAuthenticationManager().authenticate(new SamlAuthenticationToken(accessToken));
    }

}

The filter is then setup as a Spring bean, along with an authentication provider which I’ll discuss shortly:

import SamlAuthenticationFilter
import SamlAuthenticationProvider

beans = {
    samlAuthenticationFilter(SamlAuthenticationFilter) {
        authenticationManager = ref('authenticationManager')
        sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
        authenticationSuccessHandler = ref('authenticationSuccessHandler')
        authenticationFailureHandler = ref('authenticationFailureHandler')
        rememberMeServices = ref('rememberMeServices')
        authenticationDetailsSource = ref('authenticationDetailsSource')
    }

    samlAuthenticationProvider(SamlAuthenticationProvider) {
        sAMLAuthenticationService = ref('SAMLAuthenticationService')
        sAMLSettingsService = ref('SAMLSettingsService')
        userDetailsService = ref('userDetailsService')
        passwordEncoder = ref('passwordEncoder')
        userCache = ref('userCache')
        saltSource = ref('saltSource')
        preAuthenticationChecks = ref('preAuthenticationChecks')
        postAuthenticationChecks = ref('postAuthenticationChecks')
    }
}

And the bean is then registered as a filter in the Bootstrap:

import org.codehaus.groovy.grails.plugins.springsecurity.SecurityFilterPosition
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils

class BootStrap {

    def init = { servletContext ->
        SpringSecurityUtils.clientRegisterFilter('samlAuthenticationFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 10)
    }

    def destroy = {
    }
}

We also need to create the Token class that is used by the Filter and the Authentication Provider:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails

class SamlAuthenticationToken extends UsernamePasswordAuthenticationToken {

    String token

    public SamlAuthenticationToken(String token) {
        super(null, null);
        this.token = token;
    }

    public SamlAuthenticationToken(UserDetails principal, String samlResponse) {
        super(principal, samlResponse, principal.getAuthorities())
    }

}

And finally the AuthenticationProvider itself:

import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.core.Authentication
import sonicg.authentication.SAMLAuthenticationService

class SamlAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    Authentication authenticate(Authentication authentication) {
        def token = (SamlAuthenticationToken) authentication

        def user = // define user if credentials check out

        if (user){
            def userDetails = userDetailsService.loadUserByUsername(user.username)
            def token1 = new SamlAuthenticationToken(userDetails, token.samlResponse)
            return token1
        }else{
            return null
        }

    }

    @Override
    public boolean supports(Class authentication) {
        return (SamlAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

The last piece of the puzzle is to tell Spring to try using this authentication provider before the other standard three in Config.groovy:

grails.plugins.springsecurity.providerNames = [
        'samlAuthenticationProvider',
        'daoAuthenticationProvider',
        'anonymousAuthenticationProvider',
        'rememberMeAuthenticationProvider']
In this case it’s important that the custom filter goes first, as it’s Token is a subclass of UsernamePasswordAuthenticationToken. If the DAO provider was first it would try to authenticate the custom token before our filter gets a chance.
That’s it! Hopefully this proves useful to someone. It’s also just a first draft, and perhaps once the security requirements evolve I can refine the implementation and share what I’ve learned.

Reference: Custom AuthenticationProvider with Grails from our JCG partner Kali Kallin at the Kallin Nagelberg’s journey into the west blog.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Federico Cayrol
8 years ago

Hi, 3 questions

1) who is sending the token parameter ?? how is generated that token ??

String accessToken = request.getParameter(“sometoken”)

2) you put this comment
def user = // define user if credentials check out
i assume that here i need to load the user from my local database

3) you never define this attribute token.samlResponse , what object is this ? did you define the object or do you use the pluging ?

Back to top button