Categories
Moving to Grails

Customizing spring security userdetails

Lately I have been working on Spring security with this configuration:

  • with groups
  • database stored in tenant database

First problem I detected was that the PerTenantUserDetailsService implemented in https://johnny.prpr.no/spring-security-multitenant/ was annotated with @CurrentTenant, which is wrong.

Furthermore I wanted to add fullName to the UserDetails, for easy lookup. This was achieved by following the plugin documentation.

Then updating the UserDetailsService.

@Slf4j
class PrprTenantUserDetailsService implements GrailsUserDetailsService {

    public static final String TENANT_ID = 'tenantId'
    UserService userService

    @Override
    UserDetails loadUserByUsername(String username, boolean loadRoles) throws UsernameNotFoundException, DataAccessException {
        loadUserByUsername(username)
    }

    @Transactional(readOnly = true, noRollbackFor = [IllegalArgumentException, UsernameNotFoundException])
    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.debug("loadUserByUsername(${username})")
        UserDetails result = null
        manuallyWireServices()
        String tenantName = UserToTenant.getTenant(username)
        Tenants.withId(tenantName) {
            MDC.put(TENANT_ID,tenantName)
            User user = userService.findByUsername(username)
            if (!user) throw new NoStackUsernameNotFoundException()
            if (user) {
                result = new PrprUserDetails(user.username,
                        user.password,
                        user.enabled,
                        !user.accountExpired,
                        !user.passwordExpired,
                        !user.accountLocked,
                        securityService.getRoles(username),
                        user.id,
                        user.name)
            }
            MDC.put(TENANT_ID, '')
        }
        log.debug("loadUserByUsername($username) == $result")
        result
    }

    private void manuallyWireServices() {
        if (userService) {
            return
        }
        GrailsApplication grailsApplication = Holders.grailsApplication
        userService = (UserService) grailsApplication.mainContext.getBean('userService')
    }

}

Beware that the authorities parameter sent to UserDetails is all the roles, so when using groups you will have to collect roles from the users group memberships.