====== 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 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.