SpringOne 2GX Sample Apps – Spring Security LDAP Login
This is the second in a series of posts making the demo applications that I used for my SpringOne 2GX presentations available. I’ll describe here how to create a Grails
application using the Spring Security plugin that authenticates users from LDAP. This is based on topics from the Demystifying Spring Security in Grails
talk (you can download the presentation here
) but wasn’t shown there since I ran out of time.
Also refer to the plugin documentation for other tutorials here.
To create an application that authenticates users from LDAP, run
cd springone2gx_ldap
To make classpath management simpler in Eclipse/STS I create a grails-app/conf/BuildConfig.groovy
(in Grails 1.1 apps; in 1.2 this is done for you) with the line
grails.project.plugins.dir='plugins'
to keep plugins in the project root like in 1.0.x but this is optional.
Next install the plugin:
Run the create-auth-domains
script to generate the person, authority, and request map domain classes and also grails-app/conf/SecurityConfig.groovy
:
The other two scripts that the plugin provides are optional and create CRUD pages (generate-manager
) and basic user registration (generate-registration
). It’s a good idea to run generate-manager
; run generate-registration
if it’s useful to you.
As with the previous post we’ll use annotated controllers, so we’ll need to configure that, and we can delete the request map class and CRUD pages. The plugin scripts currently asssume you’ll be using request maps, so we have to run
generate-manager
and generate-registration
before deleting these.
- delete
grails-app/domain/com/burtbeckwith/springone2gx/Requestmap.groovy
- delete
grails-app/controller/RequestmapController.groovy
- delete the
grails-app/views/requestmap
directory and its GSPs - remove the
com.burtbeckwith.springone2gx.Requestmap
import fromgrails-app/controller/RoleController.groovy
- in
grails-app/conf/SecurityConfig.groovy
, disable requestmaps (useRequestMapDomainClass = false
) and enable annotations (useControllerAnnotations = true
), and remove therequestMapClass
property:security { active = true loginUserDomainClass = "com.burtbeckwith.springone2gx.User" authorityDomainClass = "com.burtbeckwith.springone2gx.Role" useRequestMapDomainClass = false useControllerAnnotations = true }
In Eclipse or STS the steps to configure the classpath are:
- add PLUGIN_DIR/src/groovy as a source folder
- add PLUGIN_DIR/src/java as a source folder
- add PLUGIN_DIR/grails-app/services as a source folder
- add these jars from PLUGIN_DIR/lib
- facebook-java-api-2.0.4.jar
- jcifs-1.2.25.jar
- spring-ldap-1.2.1.jar
- spring-ldap-tiger-1.2.1.jar
- spring-security-core-2.0.4.jar
- spring-security-core-tiger-2.0.4.jar
- spring-security-ntlm-2.0.4.jar
- spring-security-openid-2.0.4.jar
Having done all that, let’s create a secured controller to test annotations:
and add the import for the annotation, and annotate at the class level that you must be an admin to access this controller:
import org.codehaus.groovy.grails.plugins.springsecurity.Secured @Secured(['ROLE_ADMIN']) class SecureController { def index = { redirect action: foo } def foo = { render 'OK' } def bar = { render 'also OK' } }
The controller has a redirect from the default action and a second action so we can test that all methods inherit the class-level annotation.
Next lets configure LDAP. To make this a self-contained demo, we’ll use the excellent LDAP server plugin but obviously you’ll need to configure the application to connect to your LDAP server.
Install the plugin by running
and add the necessary LDAP configuration option to grails-app/conf/SecurityConfig.groovy
(at a minimum useLdap = true
)
security { ... useLdap = true ldapServer = 'ldap://localhost:10389' ldapManagerDn = 'uid=admin,ou=system' ldapManagerPassword = 'secret' ldapSearchBase = 'dc=d1,dc=example,dc=com' ldapSearchFilter = '(uid={0})' ldapGroupSearchBase = 'ou=groups,dc=d1,dc=example,dc=com' ldapGroupSearchFilter = 'uniquemember={0}' ldapUsePassword = false }
The LDAP plugin requires one or more configured LDAP servers in grails-app/conf/Config.groovy
and we’ll need just one:
ldapServers { d1 { base = 'dc=d1,dc=example,dc=com' port = 10389 indexed = ['objectClass', 'uid', 'mail', 'userPassword', 'description'] } }
The plugin will auto-load .ldif data files with user information, so put these records in grails-app/ldap-servers/d1/data/users.ldif
:
dn: ou=groups,dc=d1,dc=example,dc=com objectclass: organizationalUnit objectclass: top ou: groups dn: cn=USER,ou=groups,dc=d1,dc=example,dc=com objectclass: groupOfUniqueNames cn: USER objectclass: top uniqueMember: cn=person1,dc=d1,dc=example,dc=com uniqueMember: cn=person2,dc=d1,dc=example,dc=com uniqueMember: cn=person3,dc=d1,dc=example,dc=com dn: cn=ADMIN,ou=groups,dc=d1,dc=example,dc=com objectclass: groupOfUniqueNames objectclass: top cn: ADMIN uniqueMember: cn=person2,dc=d1,dc=example,dc=com dn: cn=person1,dc=d1,dc=example,dc=com objectClass: uidObject objectClass: person objectClass: top objectClass: organizationalPerson uid: person1 userPassword: {SHA}44rSFJQ9qtHWTBAvrsKd5K/p2j0= cn: person1 sn: jones dn: cn=person2,dc=d1,dc=example,dc=com objectClass: uidObject objectClass: person objectClass: top objectClass: organizationalPerson uid: person2 userPassword: {SHA}KqYKj/f81HPTIeAUav2eJt85UUc= cn: person2 sn: jones dn: cn=person3,dc=d1,dc=example,dc=com objectClass: uidObject objectClass: person objectClass: top objectClass: organizationalPerson uid: person3 userPassword: {SHA}ERnP037iRzV+A0oI2ETuol9v0g8= cn: person3 sn: jones
Spring Security will by default convert LDAP groups (‘groupOfUniqueNames’) to roles, prefixing the group names with ROLE_, so this data creates three users; person1, person2, and person3 (with passwords ‘password1’, ‘password2’, and ‘password3’ respectively), all with ROLE_USER and person2 with ROLE_ADMIN.
Since LDAP is only managing authentication details we need local data in the database; create the corresponding entries in BootStrap:
import com.burtbeckwith.springone2gx.User class BootStrap { def init = { servletContext -> new User(username: 'person1', enabled: true).save() new User(username: 'person2', enabled: true).save() new User(username: 'person3', enabled: true).save(flush: true) } def destroy = {} }
Since LDAP is handling authentication, we can (partially) remove password-related fields from User.groovy
along with other unused fields:
class User { static hasMany = [authorities: Role] static belongsTo = Role String username String passwd = 'notused' boolean enabled static constraints = { username blank: false, unique: true } }
We need to leave in the passwd
property since GrailsDaoImpl
expects it but its value isn’t important so we’ll just hard-code it in the domain class. A custom subclass GrailsDaoImpl
or a new implementation of UserDetailsService
would remove this requirement.
Start the app using
and open http://localhost:8080/springone2gx_ldap/secure/ in a browser and it should prompt you to login. If you login as person1 or person3 you’ll be denied access since those users only have ROLE_USER but person2 has ROLE_ADMIN and will be allowed.
After successful login it’ll redirect to http://localhost:8080/springone2gx_ldap/secure/foo – verify that http://localhost:8080/springone2gx_ldap/secure/bar
is also secured by going to that in your browser.
You can download a finished application based on this discussion here
Awesome – thanks for the examples and source Burt!!
Hi Burt,
Really good tutorial. Thanks! We have a couple thousand users we’d like to authenticate and authorize against an LDAP server. Is there any way to avoid having to duplicate those users in the database? We’d like to authorize and authenticate just from LDAP and if a user has a matching entry in the database to include those roles as well.
Regards,
John
I got the sample app working, and am now trying to bind to my corporate LDAP server. I think the bind is failing because I cannot login using my username/password (credentials I know to be correct). However, although I can get DEBUG logging messages from springsecurity when the authentication request fails, I am not getting any debug messages regarding binding.
I think the parameters I am using in SecurityConfig.groovy are wrong, but I cannot debug them without visibility.
I would love to set breakpoints and walk through the spring code, but I cannot figure out how to do this using STS. I can hit breakpoints in my code and select acegi plugin classes, but not in spring code (e.g. org.springframework.security.providers.ldap.authenticator.BindAuthenticator.java).
In an ideal situation, one knows exactly what LDAP values to use with one’s LDAP database. I did not.
I was using Active Directory. Here are the values I eventually ended up using in my SecurityConfig.groovy class:
useLdap = true
ldapServer = ‘ldap://myADserver:389’
ldapManagerDn = ‘mydomain\\myuser’
ldapManagerPassword = ‘myPrivatePasswd’
ldapSearchBase = ‘OU=Someplace,DC=corp,DC=myCompany,DC=com’
ldapSearchFilter = ‘(sAMAccountName={0})’
ldapGroupSearchBase = ‘OU=Users,DC=corp,DC=myCompany,DC=com’
ldapGroupSearchFilter = ‘(member={0})’
ldapUsePassword = false
I never got the ldapGroup query to return any values, but that may be because my company does not use them that way. Instead, there was a “memberOf” attribute in the LDAP entry for each user. (I don’t know if this is a standard, a best practice or just what my company uses).
To set breakpoints in spring code, one must first download the appropriate sources jars and link to them in STS. I downloaded the jars (see below) and put them in my c:\grails-1.2.2\lib folder.
For the acegi plugin sources, I downloaded the plugin as a zip and put it in c:\grails-plugins.
To link to them, one has to know the class names of the relevant Spring classes.
Jar files:
spring-security-core-2.0.4-sources.jar
spring-ldap-1.2.1-sources.jar
Relevant classes & methods:
org.springframework.security…
…ldap.SpringSecurityLdapTemplate.searchForSingleEntry()
…ldap.SpringSecurityLdapTemplate.searchForSingleAttributeValues()
…providers.ldap.authenticator.BindAuthenticator.bindWithDn()
…userdetails.ldap.LdapUserDetailsMapper.mapUserFromContext()
org.springframework.ldap.core.LdapTemplate.search()
And from the acegi plugin zip:
grails-acegi-0.5.3.zip
org.codehaus.groovy.grails.plugins.springsecurity…
…GrailsDaoImpl.loadUserByUserName()
…ldap.GrailsUserDetailsMapper.mapUserFromContext()
I hope this helps somebody get started debugging using LDAP with the grails-acegi plugin.
Oh, and thanks a million to Burt for this article!
I am using STS. I found it necessary, after installing the acegi plugin and running “create-auth-domains” to right-click on the project, select Grails Tools -> Refresh Dependencies.