0

How do I solve the following LDAP authentication situation using Spring Security/LDAP as much as possible.

  • User belongs to one of 2 LDAP organizational units (ou): Clients or Employees

  • User belongs to one of 3 access groups (cn - groupofuniquenames) or their subgroups (cn)

So basically it would be:

  1. Finding user's DN in LDAP (client or employee)

  2. Binding user to check password

  3. Searching one by one through all 3 access groups and their subgroups to find uniquename attribute with user's DN.

I have looked into various tutorials and examples but none of them seem to relate and I was unable to combine them. It would be easier if access group was an Organizational Unit, but it's not.

The entire page and all of it's servlets are supposed to be behind authentication.

Question is a bit specific but hopefully useful for community. Any ideas or suggestions are most welcome.

The code I currently use is modified version from spring documentation.

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
    <property name="rolePrefix" value=""></property>
</bean>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
    <constructor-arg name="decisionVoters" ref="roleVoter" />
</bean>

<security:http authentication-manager-ref="ldap-auth" access-decision-manager-ref="accessDecisionManager">
    <security:intercept-url pattern="/site/**" access="LDAP-Access-Group" />
    <security:form-login 
        login-page="/login" 
        authentication-failure-url="/denied"
        username-parameter="username" 
        password-parameter="password"
        default-target-url="/site/main" />
    <security:logout 
        invalidate-session="true" 
        logout-success-url="/login" 
        logout-url="/j_spring_security_logout" />
    <security:access-denied-handler error-page="/denied" />
    <security:session-management invalid-session-url="/login">
        <security:concurrency-control max-sessions="1" expired-url="/login" />            
    </security:session-management>
</security:http>

<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <constructor-arg value="ldap://server:389/o=company,c=com"/>
</bean>

<security:authentication-manager id="ldap-auth">            
    <security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>

<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
    <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
        <constructor-arg ref="contextSource"/>
        <property name="userDnPatterns">
            <list>
                <value>uid={0},ou=Employees</value>
                <value>uid={0},ou=Clients</value>
            </list>
        </property>
    </bean>
</constructor-arg>
<constructor-arg>
    <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
        <constructor-arg ref="contextSource"/>
        <constructor-arg value="ou=Access"/>
        <property name="searchSubtree" value="true"/>
        <property name="groupRoleAttribute" value="cn" />
    </bean>
</constructor-arg>
</bean>

The above code doesn't seem to return authorities. Is there a way to send the output to debug console? Can't read LDAP logs.

Also, if I comment out the AuthoritiesPopulator, then the authentication seems to work when checking with security tags, i.e. <sec:authorize access="isAuthenticated()">logged in</sec:authorize>, but for some reason intercept-url prevent me from entering site using <security:intercept-url pattern="/site/**" access="isAuthenticated()" />. I don't understand it.

yosh
  • 3,245
  • 7
  • 55
  • 84

2 Answers2

2

Your step 1 and 2 are the practical default for LDAP authentication.

Regarding step 3: some LDAP servers maintain a reciprocal relationship for groups and their members. So when a user is added to a group, both the member attribute on the group and the memberOf attribute on the user are populated. This would simplify things because you can then retrieve the users' memberOf attribute and find your groups.

Since you are querying the groups, I assume this is not the case for you. My recommendation would be, to create a list of desired groups that contains the 3 main access groups and their subgroups. Depending on the desired flexibility, you should probably apply some caching there.

Next, query the LDAP server for all groups having your user as a member using (&(objectClass=groupOfUniqueNames)(member=cn=youruser,ou=some,o=org)) returning the DN list only, no attributes (for performance reasons).

Now you can go through the list of returned groups and see if your desired groups are among them.

This approach ensures that you only need one query total at login time (assuming you cache the groups list), instead of one per group.

mvreijn
  • 2,807
  • 28
  • 40
  • With memberOf attribute it would be too easy and I don't have it here. So basically I have to write my custom authentication manager and there is no magical Spring Security configuration that would work in this scenario? – yosh Jun 23 '14 at 12:56
  • I think that you do not have to code all of this when you use Spring. If you configure the `group-search-base` and set `role-prefix="none"` you get a list of groups that the user is a member of. However Spring assumes these are *authorizations* to be checked after *authenciation*. You would need to check the granted authorities for your groups using `LdapAuthenticationProvider.loadUserAuthorities()`. – mvreijn Jun 23 '14 at 13:58
0

It appears the main problem was with specifying search base argument in DefaultLdapAuthoritiesPopulator. Changing value to "" in DefaultLdapAuthoritiesPopulator bean solved the problem and started returning user's authorities.

<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <constructor-arg value="ldap://server:389/o=company,c=com"/>
    <property name="anonymousReadOnly" value="true"/>
</bean>

<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <constructor-arg>
        <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <constructor-arg ref="contextSource"/>
            <property name="userDnPatterns">
                <list>
                    <value>uid={0},ou=Employees</value>
                    <value>uid={0},ou=Clients</value>
                </list>
            </property>         
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
            <constructor-arg ref="contextSource"/>
            <constructor-arg value=""/>
            <property name="searchSubtree" value="true"/>
            <property name="groupRoleAttribute" value="cn"/>
            <property name="groupSearchFilter" value="uniquemember={0}"/>   
        </bean>
    </constructor-arg>
</bean>
yosh
  • 3,245
  • 7
  • 55
  • 84