Table of Contents

Adding Customized Authentication for Spring Security Plugin

This is an advance topic, we will try to make thing as simple as possible. You need to first add the spring security plugin to your app first. If not do it like this. If you do not fully understand why you need this, you should go back.

To add your own authentication method, you need to create 3 classes. They are the authentication filter, authentication token, and the authentication provider, they can be either in java or groovy.

In this example, we assume to use the user IP address for authentication. (You can actually control it in application.groovy, and no need to do such.)

Here is the summary:

  1. Creating the authentication token
  2. Creating authentication filter
  3. Creating authentication provider
  4. Register the authentication filter and provider in resources.groovy under /conf/spring directory
  5. Insert the authentication in application.groovy
  6. Insert the authentication filter in BootStrap.groovy

Creating the Authentication Token

Token will be use to check for authentication, and if an user is authenticated, it hold the granted authority. To simplify the process, we can start it with extending AbstractAuthenticationToken.

package hello

import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority

class MyToken extends AbstractAuthenticationToken {

    String ipAddress

    MyToken(String ipAddress) {
        super(null)
        this.ipAddress = ipAddress
    }

    MyToken(String ipAddress, Collection<? extends GrantedAuthority> authorities) {
        super(authorities)
        this.ipAddress = ipAddress
        super.setAuthenticated(true);
    }

    @Override
    Object getCredentials() {
        return ipAddress
    }

    @Override
    Object getPrincipal() {
        return ipAddress
    }
}

Creating Authentication Filter

This filter will intercept a request, and create the authentication token. To stay simple, we extends the AbstractAuthenticationProcessingFilter, and use the path “/login/authenticate” for detection.

package hello

import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher

import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class MyFilter extends AbstractAuthenticationProcessingFilter{

    MyFilter() {
        super(new AntPathRequestMatcher("/login/authenticate", "POST"));
    }

    @Override
    Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String sClientIP = request.getHeader("X-Forwarded-For")
        if (sClientIP == null) {
            sClientIP = request.getRemoteAddr()
        }
        MyToken myToken = new MyToken(sClientIP)
        return this.getAuthenticationManager().authenticate(myToken)
    }
}

Creating Authentication Provider

In this provider, the token will be provided (How? we will take about it later) to a method authenticate for authenticate the user. For this example, we will granted the user with local IP (127.0.0.1) a ROLE_USER role. The most import method here is authenticate.

package hello

import org.springframework.beans.factory.InitializingBean
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper

class MyProvider implements AuthenticationProvider, InitializingBean {
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();


    @Override
    Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyToken myToken = (MyToken) authentication
        boolean isLogin = false

        if (myToken.ipAddress.equals("127.0.0.1")){
            isLogin = true
        }

        if (isLogin) {
            GrantedAuthority grantedAuthority = new GrantedAuthority() {
                @Override
                String getAuthority() {
                    return "ROLE_USER"
                }
            }
            MyToken myAuthenticatedToken = new MyToken(myToken.getIpAddress(),
                    authoritiesMapper.mapAuthorities([grantedAuthority]))
            return myAuthenticatedToken
        } else {
            throw new BadCredentialsException("Bad credentials")
        }
    }

    @Override
    boolean supports(Class<?> authentication) {
        return MyToken.class.isAssignableFrom(authentication)
    }

    @Override
    void afterPropertiesSet() throws Exception {
        //check if bean init OK
    }

    void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
        this.authoritiesMapper = authoritiesMapper
    }
}

Initialize and Linking Everything Together

Now if you run the app, nothing happen. It is because we have not initialed, and linked our class with the spring security. Open resources.groovy, initialize our class as bean. The authenticationManager, and authoritiesMapper are provided by spring security plugin.

import hello.MyFilter
import hello.MyProvider
import org.springframework.boot.context.embedded.FilterRegistrationBean
import hello.UserPasswordEncoderListener
// Place your Spring DSL code here
beans = {
    userPasswordEncoderListener(UserPasswordEncoderListener)

    myFilter(MyFilter){
        authenticationManager = ref('authenticationManager')
    }
   
    myFilterDeregistrationBean(FilterRegistrationBean) {
        filter = ref('myFilter')
        enabled = false
    }
    
    myProvider(MyProvider) {
        authoritiesMapper = ref('authoritiesMapper')
    }
}

Now open application.groovy. We need to modified the default grails.plugin.springsecurity.providerNames. The default value of it is set in SpringSecurityUtils.groovy, but we can overwritten it in application.groovy. Add the following in application.groovy:

grails.plugin.springsecurity.providerNames = [
		'myProvider',
		'daoAuthenticationProvider',
		'anonymousAuthenticationProvider',
		'rememberMeAuthenticationProvider'
]

You might remove the default providers if you no longer need them.

Now, in BootStrap.groovy, register our filter by adding the following inside init:

SpringSecurityUtils.clientRegisterFilter('myFilter', SecurityFilterPosition.PRE_AUTH_FILTER.order  - 1 )

Testing

Now run the app. The default grails page should have a two links like:

Available Controllers:
    grails.plugin.springsecurity.LoginController
    grails.plugin.springsecurity.LogoutController

Click the first one will redirect you to a login page. Make sure your are accessing the site with URL http://127.0.0.1:8080. Click Login button without any username, and password. You should be redirect back to the homepage. How to know you have actually logged in? Click the grails.plugin.springsecurity.LoginController again, and it should not longer redirect you to the login form because you have already logged in. (There is NO WAY TO logout, since logout take HTTP POST method only by default)

What to Do Next?

This example is for demonstration only. And there is no error checking, the GrantedAuthority creation inside the provider is bad, the authority is hard-coded instead of using a database or a constant look up table, and the login succeed, failed redirection is bad. We do it so badly so that we can simplify the example by showing the core part only. There are a lot to do to handle the above listed problem.