Abhilash Shajan
Abhilash Shajan

Reputation: 650

spring-security-core custom authentication in grails

Iam using spring-security-core 2.0 RC5 in my grails application and I added an extra column in the user table named 'approved_fl' I just want check this flag while login, if this flag is P then i need to show a message like 'sorry your account is not yet approved by admin', if this flag is R then i need to show 'Sorry your account is rejected by admin'. How to configure my spring security plugin for this.

Upvotes: 3

Views: 1688

Answers (2)

Abhilash Shajan
Abhilash Shajan

Reputation: 650

Finally I got this.

I done the following changes in my application.

In my resources.groovy

import com.custom.auth.MyUserDetailsService
import com.custom.auth.MyPreAuthenticationChecks

beans = { 

     preAuthenticationChecks(MyPreAuthenticationChecks) 
	 userDetailsService(MyUserDetailsService)
   }

I created following groovy classes under src/groovy package

enter image description here

Here is the MyPreAuthenticationChecks.groovy

package com.custom.auth

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import com.custom.exception.AccountNotApprovedException
import com.custom.exception.AccountRejectedException

/**
 * Copy of the private class in AbstractUserDetailsAuthenticationProvider
 * to make subclassing or replacement easier.
 *
 * @author <a href='mailto:[email protected]'>Burt Beckwith</a>
 */
public class MyPreAuthenticationChecks implements UserDetailsChecker {

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	protected final Logger log = LoggerFactory.getLogger(getClass());

	public void check(UserDetails user) {
		if (!user.isAccountNonLocked()) {
			log.debug("User account is locked");

			throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
				"User account is locked"));
		}

		if (!user.isEnabled()) {
			log.debug("User account is disabled");

			throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
				"User is disabled"));
		}

		if (!user.isAccountNonExpired()) {
			log.debug("User account is expired");

			throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
				"User account has expired"));
		}
		if (user.approveFl=='P') {
			log.debug("User account is not approved");

			throw new AccountNotApprovedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.notapproved",
				"User account not yet approved"));
		}
		if (user.approveFl=='R') {
			log.debug("User account is rejected");

			throw new AccountRejectedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.rejected",
				"User account has rejected"));
		}
	}
}

Here is the MyUserDetails.groovy

package com.custom.auth

import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.GrantedAuthority

class MyUserDetails extends GrailsUser {

   final char approveFl

   MyUserDetails(String username, String password, boolean enabled,
                 boolean accountNonExpired, boolean credentialsNonExpired,
                 boolean accountNonLocked,
                 Collection<GrantedAuthority> authorities,
                 long id, char approveFl) {
      super(username, password, enabled, accountNonExpired,
            credentialsNonExpired, accountNonLocked, authorities, id)
      this.approveFl = approveFl
   }
}

Here is the MyUserDetailsService.groovy

package com.custom.auth

import com.domain.auth.User
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import grails.plugin.springsecurity.userdetails.NoStackUsernameNotFoundException
import grails.transaction.Transactional
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException

class MyUserDetailsService implements GrailsUserDetailsService {

   /**
    * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
    * one role, so we give a user with no granted roles this one which gets
    * past that restriction but doesn't grant anything.
    */
   static final List NO_ROLES = [new SimpleGrantedAuthority(SpringSecurityUtils.NO_ROLE)]

   UserDetails loadUserByUsername(String username, boolean loadRoles)
         throws UsernameNotFoundException {
      return loadUserByUsername(username)
   }

   @Transactional(readOnly=true, noRollbackFor=[IllegalArgumentException, UsernameNotFoundException])
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

      User user = User.findByUsername(username)
      if (!user) throw new NoStackUsernameNotFoundException()

      def roles = user.authorities

      // or if you are using role groups:
      // def roles = user.authorities.collect { it.authorities }.flatten().unique()

      def authorities = roles.collect {
         new SimpleGrantedAuthority(it.authority)
      }
      return new MyUserDetails(user.username, user.password, user.enabled,
            !user.accountExpired, !user.passwordExpired,
            !user.accountLocked, authorities ?: NO_ROLES, user.id,
            user.approveFl)
   }
}

Here is the AccountNotApprovedException.groovy

package com.custom.exception

import org.springframework.security.core.AuthenticationException

class AccountNotApprovedException extends AuthenticationException {
	
	  public AccountNotApprovedException(String message, Throwable t) {
		super(message, t)
	  }
	
	  public AccountNotApprovedException(String message) {
		super(message)
	  }
	
	  public AccountNotApprovedException(String message, Object extraInformation) {
		super(message, extraInformation)
	  }
	
	}

Here is the AccountRejectedException.groovy

package com.custom.exception

import org.springframework.security.core.AuthenticationException

class AccountRejectedException extends AuthenticationException {
	
	  public AccountRejectedException(String message, Throwable t) {
		super(message, t)
	  }
	
	  public AccountRejectedException(String message) {
		super(message)
	  }
	
	  public AccountRejectedException(String message, Object extraInformation) {
		super(message, extraInformation)
	  }
	
	}

Simply copy your existing spring-security-core LoginController to groovy controller package (this will override the existing one)

Now update the authfail() of LoginController like follows

def authfail() {

        String msg = ''
        def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
        if (exception) {
            if (exception instanceof AccountExpiredException) {
                msg = message(code: 'springSecurity.errors.login.expired')
            }
            else if (exception instanceof CredentialsExpiredException) {
                msg = message(code: 'springSecurity.errors.login.passwordExpired')
            }
            else if (exception instanceof DisabledException) {
                msg = message(code: 'springSecurity.errors.login.disabled')
            }
            else if (exception instanceof LockedException) {
                msg = message(code: 'springSecurity.errors.login.locked')
            }
			else if (exception instanceof AccountNotApprovedException) {
				msg = g.message(code: "springSecurity.errors.login.notapproved")
			}
			else if (exception instanceof AccountRejectedException) {
				msg = g.message(code: "springSecurity.errors.login.rejected")
			}
            else if (exception instanceof SessionAuthenticationException){
                msg = exception.getMessage()
            }
            else {
                msg = message(code: 'springSecurity.errors.login.fail')
            }
        }

Now add the corresponding g:messages (which need to be shown when the exception will throw) to your spring-security-core.properties file

Upvotes: 1

James Kleeh
James Kleeh

Reputation: 12228

You will need to implement your own custom user details service in order to put that property on the user details (principal)

http://grails-plugins.github.io/grails-spring-security-core/v3/index.html#userDetailsService

Then you will need extend the org.springframework.security.authentication.AccountStatusUserDetailsChecker with your own implementation that checks the property on the user details.

You can register that implementation with a bean in resources.groovy like so:

userDetailsChecker(MyCustomUserDetailsChecker)

Then if you want to display a custom message to the user you will need to override the def authfail() { action of the LoginController to look for the exception you throw.

Upvotes: 3

Related Questions