Grails Spring Security extending User attributes - grails

I have a project that will be an add-on to an existing ERP app. I've got SSO working, with a basic Spring Security setup (see my ticket here: Grails and CAS Basic Setup). Also, I'm not using the s2-quickstart.
I am still new to Java development, so please bear with me...I've been looking through documentation and examples and in some ways I'm more confused by what I've seen. I have a few questions:
1) This setup allows me access to the username of the logged in user, but not much else? How can I access the User object to access these properties? Right now, I am using:
def userDetails = springSecurityService.principal
then: userDetails ?.getUsername() to get the username
and: userDetails ?.getAuthorities() to get roles
This is all working, except is this the best way to access these properties?
i.e., are there other properties in the userdetails, and how do I access those?
Perhaps the answer to this depends on hwo I go about this next bit...
2) I am authenticating with CAS, but now I want to pull some additional attributes from LDAP. I have tried creating a custom UserDetailsService like this http://grails-plugins.github.io/grails-spring-security-core/docs/manual/guide/userDetailsService.html but even after I got past initial compilation erros, it still seems to require the User domain class. Must I implement a User domain class if I want to extend UserDetails with additional values?
In my case, I really want just 1 LDAP attribute - and this all seems like a lot of lifting to get it. I have looked and looked but can't find a good working/simple example of doing this...many have Gorm involved or the s2-quickstart installed.
tia,
Bean
UPDATE
Here's my config...see error at the end:
Config.groovy snip
grails.plugins.springsecurity.providerNames = ['casAuthenticationProvider']
grails.plugins.springsecurity.cas.active = true
grails.plugins.springsecurity.cas.sendRenew = false
grails.plugins.springsecurity.cas.serverUrlEncoding = 'UTF-8'
grails.plugins.springsecurity.cas.key = 'changeme'
grails.plugins.springsecurity.cas.loginUri = '/login'
grails.plugins.springsecurity.cas.serviceUrl = '${grails.serverURL}/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.serverUrlPrefix = 'https://cas2.mydomain.com:8443/cas'
grails.plugins.springsecurity.cas.proxyCallbackUrl = '${grails.serverURL}/secure/receptor'
grails.plugins.springsecurity.cas.proxyReceptorUrl = '/secure/receptor'
grails.plugins.springsecurity.logout.afterLogoutUrl = 'https://cas2.mydomain.com:8443/cas/logout?service=' + appProtocol + '://cas2.mydomain.com:' + appPort + '/' + appName + '/'
grails.plugins.springsecurity.cas.artifactParameter = 'ticket'
grails.plugins.springsecurity.cas.serviceParameter = 'service'
grails.plugins.springsecurity.cas.filterProcessesUrl = '/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.useSingleSignout = true
grails.server.loopback.url = ""
//Spring Security Core Config
grails.plugins.springsecurity.rejectIfNoRule = false // V.044::to fix redirect loop::true
grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap"
/*
* Order matters...put the most restrictive first
*/
grails.plugins.springsecurity.interceptUrlMap = [
'/js/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
'/css/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
'/images/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
'/login/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
'/logout/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
'/secure/receptor': ['IS_AUTHENTICATED_ANONYMOUSLY'], // <- allows CAS to contact the receptor
'/protected/**': ['IS_AUTHENTICATED_FULLY'],
'/unprotected/**': ['IS_AUTHENTICATED_ANONYMOUSLY','IS_AUTHENTICATED_FULLY'],
'/filtered/edit': ["hasRole('ROLE_XYZ')"],
'/filtered/create': ["authentication.uid == 'criderk'"],
'/filtered/list': ["hasRole('ROLE_44808')"],
'/filtered/index': ["hasRole('ROLE_44808')"],
'/': ['IS_AUTHENTICATED_ANONYMOUSLY','IS_AUTHENTICATED_FULLY']
]
grails.plugins.springsecurity.ldap.search.attributesToReturn = ['uid','mail']
grails.plugins.springsecurity.userLookup.userDomainClassName = 'basecas_04.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'basecas_04.UserRole'
grails.plugins.springsecurity.authority.className = 'basecas_04.Role'
resources.groovy
// Place your Spring DSL code here
beans = {
// load ldap roles from spring security
initialDirContextFactory(org.springframework.security.ldap.DefaultSpringSecurityContextSource,
"ldap://xx.xx.xx.xx:389"){
userDn = "cn=admin,dc=mydomain,dc=com"
password = "pw"
}
ldapUserSearch(org.springframework.security.ldap.search.FilterBasedLdapUserSearch,
"ou=employees,dc=mydomain,dc=com", "uid={0}", initialDirContextFactory){
}
ldapAuthoritiesPopulator(org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator,
initialDirContextFactory,"ou=groups,dc=mydomain,dc=com"){
groupRoleAttribute = "gidNumber"
groupSearchFilter = "memberUid={1}"
searchSubtree = true
rolePrefix = "ROLE_"
convertToUpperCase = true
ignorePartialResultException = true
}
ldapUserDetailsService(org.springframework.security.ldap.userdetails.LdapUserDetailsService,
ldapUserSearch,
ldapAuthoritiesPopulator)
userDetailsService(basecas_04.CustomUserDetailsService)
}
CustomUserDetails.groovy
package basecas_04
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User
class MyUserDetails extends GrailsUser {
final String mail
MyUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String mail) {
super(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities, id)
this.mail = mail
}
}
MyUserDetailsService.groovy# (does this go in services...?...or in src/groovy?)
package basecas_04
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils import org.springframework.security.core.authority.GrantedAuthorityImpl import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UsernameNotFoundException
import basecas_04.User
class CustomUserDetailsService implements GrailsUserDetailsService { static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)] UserDetails loadUserByUsername(String username, boolean loadRoles) throws UsernameNotFoundException { return loadUserByUsername(username) } UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User.withTransaction { status ->
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.mail) } } }
User.groovy (domain class)
class User {
transient springSecurityService
String username
String password
String mail
//String displayName
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
Error I'm getting
2014-02-18 08:37:48,106 [http-apr-8444-exec-9] ERROR errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /basecas_04/protected/list
No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl
Possible solutions: dn. Stacktrace follows:
groovy.lang.MissingPropertyException: No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl
Possible solutions: dn
at grails.plugins.springsecurity.SpringSecurityService.getCurrentUser(SpringSecurityService.groovy:80)
at basecas_04.ProtectedController.list(ProtectedController.groovy:22)
at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:195)
at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
at org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:65)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
Any ideas on this:
No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl

your answer probably lies in here: spring-security-ldap (if you are using this since i do believe this is what you are also using?) : https://github.com/grails-plugins/grails-spring-security-ldap/blob/master/src/java/grails/plugin/springsecurity/ldap/userdetails/GrailsLdapUserDetailsManager.java - t
Doing a quick search for :
security.ldap.userdetails.LdapUserDetailsManager
returns:
http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.html#loadUserByUsername%28java.lang.String%29
loadUserByUsername
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException,
DataAccessException
Description copied from interface: UserDetailsService
Locates the user based on the username. In the actual implementation, the search may possibly be case insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the UserDetails object that comes back may have a username that is of a different case than what was actually requested..
Specified by:
loadUserByUsername in interface UserDetailsService
Parameters:
username - the username identifying the user whose data is required.
Returns:
**a fully populated user record (never null)**

It can be a bit difficult to navigate the Spring Security bean maze. On the upside, once you have your bearings, it's a very powerful and flexible bean maze :)
For #1, you are free to override the default UserDetails class
with your own custom implementation that adds any additional
properties you need. To do this, you will want to define a custom
UserDetailsService as described in the core plugin documentation
here.
For #2, this link may help:
http://swordsystems.com/2011/12/21/spring-security-cas-ldap/

Related

Grails - Spring Security UI Email for Username

I'm using Grails 3.2.4 and am attempting to use the email property of my User class as the username for registration.
So far, I've managed to get Spring Security Core to use the email as the username using the configuration setting below:
grails.plugin.springsecurity.userLookup.usernamePropertyName='email'
However, the registration functionality doesn't seem to take this into account and won't let me register a new user using only an email and password.
I've made a few attempts at overriding the RegisterController but I continue to experience different errors regarding null usernames.
It seems like I must be missing something very simple. Any help / direction is greatly appreciated.
It appears that in version spring-security-ui-3.0.0.M2 the username property might not be override-able.
String paramNameToPropertyName(String paramName, String controllerName) {
// Properties in the ACL classes and RegistrationCode aren't currently
// configurable, and the PersistentLogin class is generated but its
// properties also aren't configurable. Since this method is only by
// the controllers to be able to hard-code param names and lookup the
// actual domain class property name we can short-circuit the logic here.
// Additionally there's no need to support nested properties (e.g.
// 'aclClass.className' for AclObjectIdentity search) since those are
// not used in GSP for classes with configurable properties.
if (!paramName) {
return paramName
}
Class<?> clazz = domainClassesByControllerName[controllerName]
String name
if (clazz) {
String key = paramName
if (paramName.endsWith('.id')) {
key = paramName[0..-4]
}
name = classMappings[clazz][key]
}
name ?: paramName
}
PS I'm getting around this now by doing something like this in my user domain class:
static transients = ["migrating"]]
String username = null
public void setEmail(String email) {
this.email = email
this.username = email
}

Grails3 spring security core 3 cannot use domain objects

I am trying to create a grails 3.1.2 website with spring security core 3.0.4.
I have used this page:
Grails Spring Security Core
I used the following scripts (replacing "website" for my project name):
grails s2-quickstart com.website User Role
grails s2-create-persistent-token com.website.PersistentLogin
grails s2-create-role-hierarchy-entry com.website.RoleHierarchyEntry
Then I added the following to my BootStrap.groovy:
if(User.count()==0) {
Date testDate = new Date()
Role userRole = new Role('ROLE_USER').save()
Role adminRole = new Role('ROLE_ADMIN').save()
User user = new User("John","password").save()
User admin = new User("Richard","password").save()
UserRole.create(admin, adminRole)
UserRole.create(user, userRole)
UserRole.withSession {
it.flush()
it.clear()
}
assert User.count() == 2
assert Role.count() == 2
assert UserRole.count() == 2
}
I received an error about not being able to find authorities of the User bean class, so I replaced the getAuthorities method with this (as adding it to the transient list didn't work):
Set<Role> getAuthorities() {
if(this.id){
return UserRole.findAllBySecUser(this)*.role
}
else{
return [] as Set
}
}
Then I try and build again, and receive an exception about the UserRole class.
ERROR org.springframework.boot.SpringApplication - Application startup failed
org.codehaus.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from this list:
public com.website.UserRole#<init>(org.springsource.loaded.C)
public com.website.UserRole#<init>(com.website.User, com.website.Role)
at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3197) ~[groovy-2.4.6.jar:2.4.6]
at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3134) ~[groovy-2.4.6.jar:2.4.6]
at groovy.lang.MetaClassImpl.createConstructorSite(MetaClassImpl.java:3434) ~[groovy-2.4.6.jar:2.4.6]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:91) [groovy-2.4.6.jar:2.4.6]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60) [groovy-2.4.6.jar:2.4.6]
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235) [groovy-2.4.6.jar:2.4.6]
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247) [groovy-2.4.6.jar:2.4.6]
at com.website.UserRole.create(UserRole.groovy:55) ~[main/:na]
How do I get past his error? I have no idea what this particular error means :/
It looks like a User had a validation error and didn't save. You're using the 2-arg constructor, but if there are more required properties than just username and password, validation will fail and save() will return null.
If you change the code to create a user to
User user = new User(..., ...)
user.save()
if (user.hasErrors()) {
println user.errors
}
then you'll see what's wrong. Add missing properties after the constructor call:
User user = new User(..., ...)
user.fullName = '...'
or switch to the traditional Map constructor to initialize it in one line:
User user = new User(username: ..., password: ..., fullName: ...)

Spring Security 4 issue using Query Method

I am using the latest spring security 4 version and it introduces a new feature to use the logged in user details directly in the query method using expression language. Here is my spring data repository code:
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select username from User u where u.username = ?#{ principal?.username }")
User findByUsername(String username);
}
In the above code, I have an entity User as below:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
#NotNull
private String password;
#Column(name = "enabled", nullable = false)
#NotNull
private Boolean enabled;
#Column(name = "role", nullable = false)
#Enumerated(EnumType.STRING)
private Role role;
//getters and setters
Also I have this entry for enabling this feature:
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
When I run the application, I get the error:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Authentication object cannot be null; nested exception is java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:381)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
Caused by: java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.security.access.expression.SecurityExpressionRoot.<init>(SecurityExpressionRoot.java:46)
at org.springframework.security.data.repository.query.SecurityEvaluationContextExtension$1.<init>(SecurityEvaluationContextExtension.java:113)
at org.springframework.security.data.repository.query.SecurityEvaluationContextExtension.getRootObject(SecurityEvaluationContextExtension.java:113)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider$EvaluationContextExtensionAdapter.<init>(ExtensionAwareEvaluationContextProvider.java:463)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider.toAdapters(ExtensionAwareEvaluationContextProvider.java:210)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider.access$000(ExtensionAwareEvaluationContextProvider.java:58)
What could be the issue. Here I am posting to check if there is any issue in using the query method. Can i use like principal.username, is that correct?
Update: When I removed the #Query from repository it works fine. That means its problem with the new spring security 4 using principal.username. Is there anything wrong in this expression?
#Query("select username from User u where u.username = ?#{ principal?.username }")
Pls try this custom class :-
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
#Override
public String getExtensionId() {
return "Security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication){};
}
}
Not sure whether you have solved the issue in the meantime, but I noticed that your query should look like:
select u from User u where u.username = ?#{ principal }
assuming your principal object is the plain username String.
If you created your own SecurityEvaluationContextExtension class, and did not implement getAuthentication() method, you might be getting this exception.
In this link, you can see original SecurityEvaluationContextExtension.java file, that implements all necessary methods.
So, you don't need to implement this class on your own. Instead, you can add below dependency to your pom file to have the original one;
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-data -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
<version>4.2.1.RELEASE</version>
</dependency>
If you are using any other dependency manager rather than maven, you can goto related maven repo and get the definition that you want.
I hope this helps.

Keep users in Config.groovy list in Grails

Is there any way to define the users that can use my application in a list in Config.groovy? This will be using Grails 2.2.3 and the latest versions of Spring Security Core and Spring Security LDAP.
We use Active Directory for authentication, and only 2 or 3 people will use this little application, so it doesn't seem worthy of making an AD Group for just this app. It would be simpler to define a list, and any time there is a new hire instead of adding them to the AD group all I have to do is add their name to the external Grails config.
I would like to do something like the following:
SomeController.groovy
#Secured("authentication.name in grailsApplication.config.my.app.usersList")
class SomeController {
}
Then in Config.groovy put this code:
my.app.usersList = ['Bill', 'Tom', 'Rick']
Is this possible? If so, is this a terrible idea? Thanks a lot.
That seems really silly. Why not have the list of users in a table? Then you can add/remove from that table without have to modify the application.
I currently do this and in my UserDetailsContextMapper I make sure the username already exists in the Users table.
You need a custom authenticator that will try to access your Active Directory and if authenticated, will look into Grails properties to check if the username is allowed to login.
This is the class that I use. I changed the code to validate the config:
class ActiveDirectoryAuthenticator {
private DefaultSpringSecurityContextSource contextFactory
private String principalSuffix = ""
def grailsApplication
public DirContextOperations authenticate(Authentication authentication) {
// Grab the username and password out of the authentication object.
String principal = authentication.getName() + "#" + principalSuffix
String password = ""
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString()
}
// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
try {
String provider = contextFactory.getUrls()[0]
Hashtable authEnv = new Hashtable(11)
authEnv.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory")
authEnv.put(Context.PROVIDER_URL, provider)
authEnv.put(Context.SECURITY_AUTHENTICATION, "simple")
authEnv.put(Context.SECURITY_PRINCIPAL, principal)
authEnv.put(Context.SECURITY_CREDENTIALS, password)
javax.naming.directory.DirContext authContext = new InitialDirContext(authEnv)
//here validate the user against your config.
if(!authentication.getName() in grailsApplication.config.adUsersAllowed) {
throw new BadCredentialsException("User not allowed.")
}
DirContextOperations authAdapter = new DirContextAdapter()
authAdapter.addAttributeValue("ldapContext", authContext)
return authAdapter
} catch ( NamingException ex ) {
throw new BadCredentialsException(ex.message)
}
} else {
throw new BadCredentialsException("Incorrect username or password")
}
}
public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* #param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory
}
public String getPrincipalSuffix() {
return principalSuffix
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* #param principalPrefix
*/
public void setPrincipalSuffix(String principalSuffix) {
if (principalSuffix != null) {
this.principalSuffix = principalSuffix
} else {
this.principalSuffix = ""
}
}
}
Declare it as your ldapAuthenticator in resources.groovy:
ldapAuthenticator(ActiveDirectoryAuthenticator) {
contextFactory = ref('contextSource')
principalSuffix = 'domain.local' //your domain suffix
grailsApplication = ref('grailsApplication')
}
The downside is that you need to restart your context when you change config.groovy
In your controllers just use #Secured('IS_AUTHENTICATED_FULLY')
I do not think you can do that because annotations are resolved at compile time and not in runtime. Config properties will be read during the application runtime so you I fear you have to end up doing:
#Secured(["authentication.name in ['Bill', 'Tom', 'Rick']"])
class SomeController {
}
If I remember correctly the #Secured annotation cannot be used for other things than comparing roles. But you should be able to do this with spring securities #PreAuthorize and #PostAuthorize annotations. When using grails the easiest way to setup these annotations is installing the spring security ACL plugin.
Within #PreAuthorize and #PostAuthorize you can use SPEL expressions which are more flexible. Unfortunatelly SPEL does not provide an in operator. However you can delegate the security check to a service:
#PreAuthorize('#securityService.canAccess(authentication)')
public void test() {
println "test?"
}
With the # symbol you can reference other beans like services within expression. Here the method securityService.canAccess() is called to evaluate if the logged in user can access this method.
To use this you have to configure a BeanResolver. I wrote some more details about configuring a BeanResolver here.
Within securityService you can now do:
class SecurityService {
def grailsApplication
public boolean canAccess(Authentication auth) {
return grailsApplication.config.myList.contains(auth.name)
}
}
In general I would not recommend to use a configuration value for validating the user in security checks. The groovy configuration will be compiled so you cannot easily add a new user without redeploying your application.

Injecting LdapTemplate to return user details inside a Grails service causes Null Pointer Exception

I've been struggling with Spring Security LDAP for a while now, and just when I finally thought I had it beaten into submission, its tripped me up again.
The scenario: I'm having users log in using their organisation credentials, which I then check against the internal user database. If they don't exist, I create them locally and then set their local authorities (I can't touch the LDAP server, only authenticate against it - so I can't add the authorities there). This works fine... but I then want to add a couple of fields from LDAP - their full name and email address, and this is where it goes wrong.
I have LDAP searches working in a controller - using the following code (I've injected ldapTemplate further up):
def fullName = ldapTemplate.search("", "(uid=" + uid + "*)", new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get("cn").get()
}
})
and this works perfectly. But when I replicate this code in a service that is called post-authentication the ldapTemplate is always null... I've tried a few different things, but I can't get it to populate properly... can anyone shed some light on why? I'm hoping its something stupid I'm doing since its 4:21am and I should have been in bed about 6 hours ago...
EDIT: Here is the service code as requested, thanks for taking a look Burt - its currently not very robust as I haven't got it working past simply creating a new local user - there is work to do on it yet.
package com.myPackage
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.ldap.core.AttributesMapper
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.apache.commons.lang.RandomStringUtils
class PostAuthHandlerService implements AuthenticationSuccessHandler {
def springSecurityService
def ldapTemplate
private AuthenticationSuccessHandler target = new SavedRequestAwareAuthenticationSuccessHandler();
private List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
def username = auth.principal.getAt("username")
User.withTransaction { status ->
// Check to see if this user exists in the local db
def user = User.findByUsername(username)
if (!user) { // User doesn't exist yet, so create and make a user...
def userRole = Role.findByAuthority('ROLE_USER') ?:
new Role(authority: 'ROLE_AM').save(failOnError: true)
user = new User(
username: username,
password: auth.getCredentials() ?: placeholderPassword(),
displayName: getLdapDetails("username", "cn") ?: "",
email: getLdapDetails("username", "mail") ?: "",
enabled: true).save(failOnError: true)
UserRole.create user, userRole
}
else {
println("--- You exist already! Updating details")
user.displayName = getLdapDetails("username", "cn") ?: ""
}
target.onAuthenticationSuccess(request, response, auth)
}
}
def getLdapDetails(username, attr) {
return ldapTemplate.search("", "(uid=$username*)", new AttributesMapper() {
#Override
public Object mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get(attr).get()
}
})
}
def placeholderPassword () {
// This is here because we also allow local logins for some users.
// If the password is not available, then create a big ugly one...
return org.apache.commons.lang.RandomStringUtils.randomAlphanumeric(128)
}
public void proceed(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
target.onAuthenticationSuccess(request, response, auth)
}
}
2nd Edit: I've been trying various different things - including trying to use springSecurityService.reauthenticate to get the local user to be used during the session rather than the LDAP one - and this bean is null as well... it seems like I can't inject any beans into my service at all.
I eventually found this post: http://burtbeckwith.com/blog/?p=993 which gave me a workaround and I now have it working... but I'd still like to know why it didn't in the first place... I have so much to learn in Grails - and its hard to know where to start.
Cheers in advance
Steve
How are you injecting authenticationSuccessHandler ? You should try defining authenticationSuccessHandler in resources.xml
Grails 1.3.5 and Spring Security Core

Resources