====== 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 [[adding_spring_security_plugin|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: - Creating the authentication token - Creating authentication filter - Creating authentication provider - Register the authentication filter and provider in resources.groovy under /conf/spring directory - Insert the authentication in application.groovy - 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 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.