10 Custom UserDetailsService - Reference Documentation
Authors: Burt Beckwith, Beverley Talbott
Version: 2.0.0
10 Custom UserDetailsService
When you authenticate users from a database using DaoAuthenticationProvider (the default mode in the plugin if you have not enabled OpenID, LDAP, and so on), an implementation of UserDetailsService is required. This class is responsible for returning a concrete implementation of UserDetails. The plugin providesgrails.plugin.springsecurity.userdetails. GormUserDetailsService
as its UserDetailsService
implementation and grails.plugin.springsecurity.userdetails. GrailsUser
(which extends Spring Security's User) as its UserDetails
implementation.You can extend or replace GormUserDetailsService
with your own implementation by defining a bean in grails-app/conf/spring/resources.groovy
(or resources.xml
) with the same bean name, userDetailsService
. This works because application beans are configured after plugin beans and there can only be one bean for each name. The plugin uses an extension of UserDetailsService
, grails.plugin.springsecurity.userdetails. GrailsUserDetailsService
, which adds the method UserDetails loadUserByUsername(String username, boolean loadRoles)
to support use cases like in LDAP where you often infer all roles from LDAP but might keep application-specific user details in the database. Create the class in src/groovy
and not in grails-app/services
- although the interface name includes "Service", this is just a coincidence and the bean wouldn't benefit from being a Grails service.In the following example, the UserDetails
and GrailsUserDetailsService
implementation adds the full name of the user domain class in addition to the standard information. If you extract extra data from your domain class, you are less likely to need to reload the user from the database. Most of your common data can be kept along with your security credentials.This example adds in a fullName
field. Keeping the full name cached avoids hitting the database just for that lookup. GrailsUser
already adds the id
value from the domain class to so we can do a more efficient database load of the user. If all you have is the username, then you need to call User.findByUsername(principal.username)
, but if you have the id you can call User.get(principal.id)
. Even if you have a unique index on the username
database column, loading by primary key is usually more efficient because it takes advantage of Hibernate's first-level and second-level caches.There is not much to implement other than your application-specific lookup code:package com.mycompany.myappimport grails.plugin.springsecurity.userdetails.GrailsUserimport org.springframework.security.core.GrantedAuthority import org.springframework.security.core.userdetails.Userclass MyUserDetails extends GrailsUser { final String fullName MyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities, long id, String fullName) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id) this.fullName = fullName } }
package com.mycompany.myappimport grails.plugin.springsecurity.SpringSecurityUtils import grails.plugin.springsecurity.userdetails.GrailsUser import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService import grails.transaction.Transactional import org.springframework.security.core.authority.GrantedAuthorityImpl import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UsernameNotFoundExceptionclass 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 GrantedAuthorityImpl(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 UsernameNotFoundException( 'User not found', username) def authorities = user.authorities.collect { new GrantedAuthorityImpl(it.authority) } return new MyUserDetails(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired, !user.accountLocked, authorities ?: NO_ROLES, user.id, user.firstName + " " + user.lastName) } }
authorities
collection. There are obviously no database updates here but this is a convenient way to keep the Hibernate Session
open to enable accessing the roles.To use your implementation, register it in grails-app/conf/spring/resources.groovy
like this:beans = { userDetailsService(com.mycompany.myapp.MyUserDetailsService) }
grails.plugin.springsecurity.userdetails. GormUserDetailsService
- the methods are all protected so you can override as needed.This approach works with all beans defined in SpringSecurityCoreGrailsPlugin.doWithSpring()
- you can replace or subclass any of the Spring beans to provide your own functionality when the standard extension mechanisms are insufficient.Flushing the Cached Authentication
If you store mutable data in your customUserDetails
implementation (such as full name in the preceding example), be sure to rebuild the Authentication
if it changes. springSecurityService
has a reauthenticate
method that does this for you:class MyController { def springSecurityService def someAction() { def user = … // update user data user.save() springSecurityService.reauthenticate user.username … } }