I have a problem to get user authentication running in a Grails application with spring-security and LDAP.
The connection to LDAP works fine, I get results. But I didn't get it managed that the the user can log in and that the data is saved in the local database.
I have changed/created the following files:
config.groovy
grails.plugin.springsecurity.ldap. context.managerDn = 'USERNAME'
grails.plugin.springsecurity.ldap. context.managerPassword = 'PASSWORD'
grails.plugin.springsecurity.ldap. context.server ='ldap://LDAPSERVER:389/'
grails.plugin.springsecurity.ldap. authorities.ignorePartialResultException = true // typically needed for Active Directory
grails.plugin.springsecurity.ldap. search.base = 'DC=example,DC=com'
grails.plugin.springsecurity.ldap. search.filter='(sAMAccountName={0})' // for Active Directory you need this
grails.plugin.springsecurity.ldap. search.searchSubtree = true
grails.plugin.springsecurity.ldap.authorities.groupSearchBase ='DC=example,DC=com'
grails.plugin.springsecurity.ldap.authorities.groupSearchFilter = 'member={0}'
grails.plugin.springsecurity.ldap.authorities.retrieveDatabaseRoles = false
grails.plugin.springsecurity.ldap. auth.hideUserNotFoundExceptions = false
grails.plugin.springsecurity.ldap. search.attributesToReturn = ['mail', 'displayName', 'title', 'fullname'] // extra attributes you want returned; see below for custom classes that access this data
grails.plugin.springsecurity.providerNames = ['ldapAuthProvider']
grails.plugin.springsecurity.ldap.useRememberMe = false
grails.plugin.springsecurity.ldap.authorities.defaultRole = 'ROLE_USER'
grails.plugin.springsecurity.ldap.mapper.userDetailsClass = 'CustomUserDetails'
src/grovvy/CustomUserDetailsMapper.grovvy
package com.example
import com.example.CustomUserDetails
import org.springframework.ldap.core.DirContextAdapter
import org.springframework.ldap.core.DirContextOperations
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper
import groovy.sql.Sql
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.authentication.DisabledException
class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private static final List NO_ROLES = [new SimpleGrantedAuthority("ROLE_USER")]
def dataSource
#Override
public CustomUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authority) {
username = username.toLowerCase()
User.withTransaction {
User user = User.findByUsername(username)
String firstName = ctx.originalAttrs.attrs['givenname'].values[0]
String lastName = ctx.originalAttrs.attrs['sn'].values[0]
def roles
if(!user){
user = new User(username: username, enabled: true, firstName: firstName, lastName: lastName)
user.save(flush: true)
}
else {
user = User.findByUsername(username)
user.firstName = firstName
user.lastName = lastName
user.save(flush)
}
roles = user.getAuthorities()
}
if( !user.enabled )
throw new DisabledException("User is disabled", username)
def authorities = roles.collect { new SimpleGrantedAuthority(it.authority) }
authorities.addAll(authority)
def userDetails = new CustomUserDetails(username, user.password, user.enabled, false, false, false, authorities, user.id, user.firstName, user.lastName)
return userDetails
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
}
}
src/grovvy/CustomUserDetails.groovy
package com.example
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User
class CustomUserDetails extends User{
final String firstName
final String lastName
CustomUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String firstName, String lastName) {
super(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities, id)
this.firstName = firstName
this.lastName = lastName
}
}
src/groovy/CustomUserDetailsService.groovy
package com.example
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
class CustomUserDetailsService 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("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 SimpleGrantedAuthority(it.authority)}
return new CustomUserDetails(user.username, user.password, user.enabled,
!user.accountExpired, !user.passwordExpired,
!user.accountLocked, authorities ?: NO_ROLES, user.id,
user.firstName, user.lastName)
} as UserDetails
}
}
conf/resources.groovy
// Place your Spring DSL code here
import com.example.CustomUserDetailsContextMapper
import com.example.CustomUserDetailsService
beans = {
userDetailsService(CustomUserDetailsService)
ldapUserDetailsMapper(CustomUserDetailsContextMapper) {
dataSource = ref("dataSource")
}
}
When I run with this configuration and try to login I get the following error message:
Message: object references an unsaved transient instance - save the transient instance before flushing: com.example.User; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.User
I had the same problem. The error message states that the User instance wasn't saved. I fixed it by changing the following line in the CustomUserDetailsMapper.grovvy
user = new User(username: username, enabled: true, firstName: firstName, lastName: lastName)
to
user = new User(username: username, enabled: true, firstName: firstName, lastName: lastName, accountLocked: false, passwordExpired: false, accountExpired: false, password: "test")
and by adding a firstName and lastName to the User domain class.
As you can see I just added some default values to user which is supposed to be created. It doesn't matter that the password is always set to "test". It won't be used because you are using LDAP.
For anyone that instead modified the generated user class from spring security(after running the quickstart script) and got the same error, I have added the nullable: true in the static constraints in the domain user class to all of your custom properties - in this case firstName and lastName. This allows you to save the instance without setting all of the properties explicitly.
Hope this helps someone!
static constraints = {
username blank: false, unique: true
password blank: false
fname nullable: true
lname nullable: true
}
Related
I have the domain:
User hasOne RoleGroup hasMany Role
Exemples:
RoleGroup: Admin, Professional, Client, ...
Role: ROLE_ACTION_1, ROLE_ACTION_2, ...
How I check if a user has a RoleGroup with annotation #Secured?
I need to check if user contains all roles of RoleGroup?
User class:
class User implements Serializable {
private static final long serialVersionUID = 1
static constraints = {
password blank: false, password: true
username blank: false, unique: true
}
static mapping = {
password column: '`password`'
version false
table schema: "CA"
}
static transients = ['springSecurityService']
transient springSecurityService
transient boolean enabled = true
transient boolean accountExpired
transient boolean accountLocked
transient boolean passwordExpired
String username
String password
RoleGroup profile
Set<RoleGroup> getAuthorities() {
[profile]
}
}
RoleGroup class:
class RoleGroup implements Serializable {
private static final long serialVersionUID = 1
String name
Set<Role> getAuthorities() {
RoleGroupRole.findAllByRoleGroup (this)*.role
}
}
I think you have not fully grasped spring security.
When using annotation - first annotation must be enabled in the config - this is the case by default.
You then secure either an entire controller or a controller action using something like this
#Secured(['ROLE_ADMIN', 'ROLE_USER'])
It has no way of working out all of what the user has as authority groups.
Although in the code you pasted in the RoleGroup class you have :
getAuthorities()
I have tweaked my User domain class and added the following:
Set<RoleGroup> getAuthorities() {
UserRoleGroup.findAllByUser(this)*.roleGroup
}
Set<RoleGroup> getAuthoritiesNames() {
UserRoleGroup.findAllByUser(this)*.roleGroup?.name
}
So when I have a user
i.e. User user=User.get(1L)
def authorities = user.getAuthorities()
println "user ${user} has ${authorities}"
which is a list containing all the authorities
if (authorities.contains('ROLE_USER')) {
println "WOOHOO"
}
With spring security you could also use it within gsps:
<sec:ifAllGranted roles="ROLE_ADMIN">
show something
</sec:ifAllGranted>
So back to your question:
You have :
Set<RoleGroup> getAuthorities() {
[profile]
}
Is that something you have in put in place ?
From where it is :
class RoleGroup implements Serializable {
private static final long serialVersionUID = 1
String name
Set<Role> getAuthorities() {
RoleGroupRole.findAllByRoleGroup (this)*.role
}
}
This should list you all the authorities
User user = User.get(1L)
def authorities = user?.profile?.getAuthorities()
I'm trying to replace the username with "email" and the password with "motDePasse" but I can't figure out how to do It: I tried to replace every old name by new name in the Person class and I added the following configuration to
my application.groovy :
grails.plugin.springsecurity.userLookup.usernamePropertyName= 'email'
grails.plugin.springsecurity.userLookup.passwordPropertyName= 'motDePasse'
but it doesn't work. I'm using grails 3.1.5, anyone can help me please?
the "Custom UserDetailsService" part of the documentation doesn't show how to replace attributes.
Thank you
I ended up with keeping the default password and replacing the username with email, I had simply to replace every "username" with "email" in the Person class:
package ma.ac.uir.ecine.authentification
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
#EqualsAndHashCode(includes='email')
#ToString(includes='email', includeNames=true, includePackage=false)
class Personne implements Serializable {
private static final long serialVersionUID = 1
transient springSecurityService
String email
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
Personne(String email, String password) {
this()
this.email = email
this.password = password
}
Set<Role> getAuthorities() {
PersonneRole.findAllByPersonne(this)*.role
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
static transients = ['springSecurityService']
static constraints = {
password blank: false, password: true
email blank: false, unique: true
}
static mapping = {
password column: '`password`'
}
}
and add
grails.plugin.springsecurity.userLookup.usernamePropertyName= 'email'
to application.groovy
I couldn't replace the password.
This is my user class that has Spring Security on it
package rms
import java.util.Date;
import java.util.Set;
import enums.EmployeeStatus;
class User {
transient springSecurityService
String username
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
String firstName
String lastName
String middleName
String contactNumber
String position
String emailAddress
String employeeID
Date dateOfBirth
EmployeeStatus employeeStatus
int age
byte [] picture
static hasMany = [employeeReport: EmployeeReport]
static constraints = {
picture maxSize:20* 1024 * 1024
dateOfBirth nullable: true
employeeStatus blank: false
position blank: false
contactNumber blank: false
emailAddress blank: false, matches: "([a-z0-9_.-]+)#([da-z.-]+).([a-z.]{2,6})", email: true
age min: 18
username blank: false, unique: true
password blank: false, password: true
}
static mapping = { password column: '`password`' }
Set<SecRole> getAuthorities() {
SecUserSecRole.findAllBySecUser(this).collect { it.secRole } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
String toString(){
return "SecUser $username"
}
}
I tried this tag <sec:loggedInUserInfo field="username"/> and it works fine but this doesn't work <sec:loggedInUserInfo field="firstName"/>. It gives a
No such property: firstName for class: org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
Is there any other way to display the other properties of the current logged in user?
The loggedInUserInfo can only access data from the "principal", which is typically an instance of GrailsUser. The data includes username, id, the assigned role names, and a few booleans about whether the user is enabled, the account is locked, etc. It's easy to subclass GrailsUser and create your own UserDetailsService and capture other data from the user domain class during authentication, and store that in the GrailsUser subclass to make it available to this tag; see http://grails-plugins.github.io/grails-spring-security-core/docs/manual.1273/guide/11%20Custom%20UserDetailsService.html for more info.
This works well if the data is read-only since it will be cached until the user logs out or the session expires. If the data that you want to display can change, retrieve the user instance and add it to the model map you return from the controller action:
class MyController {
def springSecurityService
def theAction() {
...
def user = springSecurityService.currentUser
[user: user, foo: 5, bar: "whatever", ...]
}
}
and then you can display whatever you want in the GSP, e.g. ${user.firstName}
I have installed the grails Spring-Security plugin:
plugins {
compile ':spring-security-core:2.0-RC2'
}
Then I used the grails s2-quickstart com.jane Person Role command to create the needed domain classes.
As I have my own User class I refactored the code to use my User class:
package com.jane
class User {
transient springSecurityService
String email
String name
String password
Boolean isAgreeTerms = false
Date agreeTermsDt
Boolean isActive = false
Boolean isBlocked = false
Date dateCreated
Integer createdBy = 0
Date lastUpdated
Integer modifiedBy = 0
static transients = [ 'springSecurityService' ]
static hasMany = [ userProductTier: UserProductTier ]
static mapping = {
id column: "userID"
dateCreated column: 'createdDt'
lastUpdated column: 'modifiedDT'
}
static constraints = {
email blank: false, email: true, unique: true, size: 5..100
name blank: false, size: 3..50
password blank: false
}
void beforeInsert() {
if ( isAgreeTerms ) {
agreeTermsDt = new Date()
}
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
I then modified the config.groovy file to:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.jane.User'
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'email'
grails.plugin.springsecurity.userLookup.enabledPropertyName = 'isActive'
grails.plugin.springsecurity.userLookup.accountExpiredPropertyName = 'isBlocked'
grails.plugin.springsecurity.userLookup.accountLockedPropertyName = 'isBlocked'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.jane.UserRole'
grails.plugin.springsecurity.authority.className = 'com.jane.Role'
I can create users fine, verify they are in the database, have verified that encodePassword is called once. But every time I try to login I get the following error:
Sorry, we were not able to find a user with that username and
password.
And here is the service method to create users:
User createTeamLeader( String name, String email, String password, Boolean isAgreeTerms, Integer productTierId ) {
User user = new User( name: name, email: email, password: password, isAgreeTerms: isAgreeTerms, isActive: true)
UserProductTier userProductTier = new UserProductTier( productTierId: productTierId )
user.addToUserProductTier( userProductTier )
user.save()
UserRole.create( user, Role.findByAuthority('ROLE_USER'), true )
UserRole.create( user, Role.findByAuthority('ROLE_LEAD'), true )
user
}
Often adding debug logging helps, since Spring Security logs a lot at the debug level:
log4j = {
...
debug 'org.springframework.security'
}
In this case it didn't show the problem, so I added an event listener to see if there was information in the failure event:
grails.plugin.springsecurity.useSecurityEventListener = true
grails.plugin.springsecurity.onAbstractAuthenticationFailureEvent = { e, appCtx ->
println "\nERROR auth failed for user $e.authentication.name: $e.exception.message\n"
}
That displayed this:
ERROR auth failed for user ernie#ss.com: No such property: authorities for class: com.jane.User
When you made changes in the class, you removed the getAuthorities method that was in the original version, and is used by the UserDetailsService to determine granted roles during authentication. Adding it back got things working:
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
We are creating a grails aplication where we want the user to log in using their Active Directory credentials. Additionally, we want to give the business owner of this application the ability to control who has access to certain links (actions). Because of this we are using the following plugins in our grails application:
Spring Security Core
Spring Security LDAP
Spring Security UI
Because we want to empower the business user to create customized roles with certain permissions(actions) on the fly when necessary, we think that the best Spring Security Configuration is the Requestmap database based approach
So far we have accomplished as follows:
We are being able to authenticate against Active Directory successfully.
We have also been able to create different Request Mappings for different roles (ROLE_XXX) through the UI interface of the spring-security-ui plugin
Problems/Questions
The spring-security-core plugin created the following tables:
PERSON
AUTHORITY
PERSON_AUTHORITY
REQUESTMAP
These are the tables that support the creation of Roles, the assignment of URLs to roles. However, the Person_Authoritity table as the convention name implies it's a many to many relationship between a PERSON and an AUTHORITY (ROLE) since a person can potentially have more than one role. My problem is that I do not have a Person because the person already exists in Active Directory (an external source) and it was not created in the application.
Is there a way to make the authenticated user to be the PERSON ? The spring security solution requires that Person row or object however you prefer to refer to it.
I have also posted the question here:
http://grails.1312388.n4.nabble.com/Issues-integrating-LDAP-Authentication-with-Requestmap-to-Secure-URLs-td4644040.html
Thanks,
So you need to essentially map the AD user to a Person.
Here are the 3 classes you need in src/groovy. Obviously modify them as needed:
package yourpackagename
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.springframework.security.core.GrantedAuthority
class CustomUserDetails extends GrailsUser{
final String firstName
final String lastName
CustomUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String firstName, String lastName) {
super(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities, id)
this.firstName = firstName
this.lastName = lastName
}
}
package yourpackagenamehere
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
class CustomUserDetailsService 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)
}
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 CustomUserDetails(user.username, user.password, user.enabled,
!user.accountExpired, !user.passwordExpired,
!user.accountLocked, authorities ?: NO_ROLES, user.id,
user.firstName, user.lastName)
} as UserDetails
}
}
package yourpackagenamehere
import groovy.sql.Sql
import org.springframework.ldap.core.DirContextAdapter
import org.springframework.ldap.core.DirContextOperations
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.core.GrantedAuthority
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.authentication.DisabledException
class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
def dataSource
#Override
public CustomUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authority) {
username = username.toLowerCase()
User user = User.findByUsername(username)
String firstName = ctx.originalAttrs.attrs['givenname'].values[0]
String lastName = ctx.originalAttrs.attrs['sn'].values[0]
def roles
User.withTransaction {
if(!user){
user = new User(username: username, enabled: true, firstName: firstName, lastName: lastName)
user.save(flush: true)
}
else {
user = User.findByUsername(username)
user.firstName = firstName
user.lastName = lastName
user.save(flush: true)
}
roles = user.getAuthorities()
}
if ( !user.enabled )
throw new DisabledException("User is disabled", username)
def authorities = roles.collect { new GrantedAuthorityImpl(it.authority) }
authorities.addAll(authority)
def userDetails = new CustomUserDetails(username, user.password, user.enabled, false, false, false, authorities, user.id, user.firstName, user.lastName)
return userDetails
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
}
}
Under configuration in spring/resources.groovy :
import yourpackagenamehere.CustomUserDetailsService
import yourpackagenamehere.CustomUserDetailsContextMapper
beans = {
userDetailsService(CustomUserDetailsService)
ldapUserDetailsMapper(CustomUserDetailsContextMapper) {
dataSource = ref("dataSource")
}
}
Under Config.groovy, here are my settings:
grails.plugins.springsecurity.ldap.context.managerDn = 'CN=username,OU=People,DC=foo,DC=com'
grails.plugins.springsecurity.ldap.context.managerPassword = 'password'
grails.plugins.springsecurity.ldap.context.server = 'ldap://foo.com:389/'
grails.plugins.springsecurity.ldap.authorities.ignorePartialResultException = true
grails.plugins.springsecurity.ldap.search.base = 'ou=People,dc=foo,dc=com'
grails.plugins.springsecurity.ldap.search.filter="sAMAccountName={0}"
grails.plugins.springsecurity.ldap.search.searchSubtree = true
grails.plugins.springsecurity.ldap.auth.hideUserNotFoundExceptions = false
grails.plugins.springsecurity.ldap.search.attributesToReturn = null
grails.plugins.springsecurity.providerNames = ['ldapAuthProvider', 'anonymousAuthenticationProvider']
grails.plugins.springsecurity.ldap.mapper.userDetailsClass = 'CustomUserDetails'
grails.plugins.springsecurity.ldap.authorities.retrieveGroupRoles = true
grails.plugins.springsecurity.ldap.authorities.retrieveDatabaseRoles = true
grails.plugins.springsecurity.ldap.authorities.groupSearchBase ='dc=foo,dc=com'
grails.plugins.springsecurity.ldap.authorities.groupSearchFilter = 'member={0}'