Active Directory nested groups with Spring Security and Grails
dnr 6 novembre 2012 2 commentaires Lien permanent
Preliminary note: although this article is about handling nested groups from Active Directory in your Grails application, the solution for a Plain Old Java Application with Spring Security is the same, so keep reading.
For authenticating and authorising your users against an LDAP or an Active Directory (AD) in your Grails application, the SpringSecurity plugin and its LDAP minion is your one-stop shop.
It's simply matter of configuring the various properties, as described in the plugin documentation. Our configuration looks like this:
grails.plugins.springsecurity.ldap.context.server = 'ldap://some.ip:389'
grails.plugins.springsecurity.ldap.authorities.groupSearchBase =
'OU=Security Groups,OU=MyBusiness,DC=ugroup,DC=local'
grails.plugins.springsecurity.ldap.authorities.groupSearchFilter = '(member={0})'
grails.plugins.springsecurity.ldap.authorities.groupRoleAttribute = 'cn'
grails.plugins.springsecurity.ldap.search.base = 'OU=SBSUsers,OU=Users,OU=MyBusiness,DC=ugroup,DC=local'
grails.plugins.springsecurity.ldap.search.filter = '(sAMAccountName={0})'
grails.plugins.springsecurity.ldap.search.attributesToReturn =
['proxyAddresses', 'cn', 'sn', 'givenName', 'mail']
grails.plugins.springsecurity.ldap.authenticator.attributesToReturn =
['proxyAddresses', 'cn', 'sn', 'givenName', 'mail']
We've been using it happily for about a year. Until the System Administrator decided to re-organize the AD groups to better reflect the company structure by introducing nested groups.
Until now, any user would belong directly to one or several groups. For instance:
Alicia
memberOf "Research"
memberOf "Research Management"
Jonathan
memberOf "Research"
Relying on the default group-to-role mapping provided by the plugin, our users have the following roles:
Alicia: ROLE_RESEARCH_MANAGEMENT, ROLE_RESEARCH Jonathan: ROLE_RESEARCH
If we have a controller and we want to grant access to employees in the research department, we annotate it:
@Secured('ROLE_RESEARCH')
public ResearchController {
...
}
Works for both Alicia and Jonathan. Easy for us developers but less convenient for the sysadmin. There is an obvious redundancy in the AD data. In order to minimize maintenance and better reflect the corporate hierarchy, IT decided to re-organize the AD as follows:
Alicia
memberOf "Research Management"
Jonathan
memberOf "Research"
Research Management
memberOf "Research"
Alicia can still be considered a member of both "Research Management" and "Research", but with our existing mapping, we're out of luck: only the "direct" group "Research Management" is mapped, leaving the nested group "Research" unmapped.
Alicia: ROLE_RESEARCH_MANAGEMENT Jonathan: ROLE_RESEARCH
Googling for it, I found this very interesting issue in the S2 bug tracker. It contains a reference to MSDN documentation. Special filters allow you to tweak your search, including for our nested groups:
1.2.840.113556.1.4.1941
LDAP_MATCHING_RULE_IN_CHAIN
This rule is limited to filters that apply to the DN. This is a special "extended match operator that walks the chain of ancestry in objects all the way to the root until it finds a match.
Ah, magic search string... Nevermind. I was first tricked by the Jira issue mentioned above and first thought I had to roll my own AuthorityPopulator to feed the Grails plugin. But no, it's actually just matter of copy-paste!
I changed my group search filter from:
grails.plugins.springsecurity.ldap.authorities.groupSearchFilter = '(member={0})'
to
grails.plugins.springsecurity.ldap.authorities.groupSearchFilter =
'(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={0}))'
And that's it, Alicia has retrieved her previous roles:
Alicia: ROLE_RESEARCH_MANAGEMENT, ROLE_RESEARCH
All is well. This little trick was worth a blog post. Thanks to Rick Jensen, the author of the precious S2 issue, for the source of inspiration and the pointers.
Commentaires
This post is made of pure awesomeness!
I have tried accomplishing this for a few hours now, and you have absolutely saved me a ton of work. Merci!
Hm, appears that the information can also be found if one bothers to read the configuration notes in http://grails-plugins.github.com/gr... Still, I saw it here first :-)