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:
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 } }
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) } }
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 } }
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 )
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)
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.