Spring Security 3.0 with Active Directory LDAP redux…plus remember me functionality

A while back, I released my spring security configuration for doing Spring Security with Active Directory. I have since made a few changes to my configuration that has simplified it and I also got the remember me functionality working. For those of you who don’t know, remember me is a way to tell the application to remember a user for a given length of time and expire when that time is up to make them relogin.

The remember me functionality came in real handy for us since we have so much Ajax going on. It was a pain for users when they would time out after 15 minutes and immediately get redirected to the login page. I also made my configuration utilize a database to store the saved user data instead of allowing it to create everything in a cookie on the user’s machine which is extremely un-secure. I use this technique in two applications. One of them uses an in-memory database mapped with hibernate and the other uses a physical Oracle database. In order to make the PersistentToken storage for Spring 3.0 work, you need to create a table. Below the configuration is a hibernate class you can use to automatically create the table for you. I mapped the table with hibernate so that I could create a screen that tracks all logged in users, when they logged in, etc. It also gives me the ability to play God and log people out just by deleting their user record.

Another reason I chose the Persistent token mechanism is that I couldn’t get the plain token mechanism to work.

Well enough of my chatter and let’s look at my updated Active Directory and LDAP configuration with Spring 3.0. Take note that this configuration is a hybrid configuration between namespace and bean configurations. The key elements to making this configuration sing are in bold.

<beans xmlns="http://www.springframework.org/schema/beans&quot;
xmlns:context=”http://www.springframework.org/schema/context&#8221;
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xmlns:aop=”http://www.springframework.org/schema/aop&#8221;
xmlns:security=”http://www.springframework.org/schema/security&#8221;
xmlns:tx=”http://www.springframework.org/schema/tx&#8221;
xsi:schemaLocation=”
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd”&gt;

<security:global-method-security
secured-annotations=”enabled” jsr250-annotations=”enabled”
pre-post-annotations=”enabled” />

<security:http use-expressions=”true” auto-config=’false’
realm=”project” entry-point-ref=”authenticationProcessingFilterEntryPoint”>

<security:intercept-url pattern="/login.jsp*"
filters=”none” />

<security:intercept-url pattern="/logout.jsp*"
filters=”none” />

<security:form-login login-page='/login.jsp'
default-target-url=’/index.jsp’ always-use-default-target=’true’ />

<!–
<security:intercept-url pattern="/*.jsp"
access=”hasRole(‘ROLE_WEB_PROMISES_INQUIRY’)” />
–>

<security:intercept-url pattern="/portal.jsp"
access=”hasAnyRole(‘ROLE_USER’,'ROLE_iArchiveViewer_Backoffice’,'ROLE_SUPPORT’)” />

<security:intercept-url pattern="/"
access=”hasAnyRole(‘ROLE_USER’,'ROLE_iArchiveViewer_Backoffice’,'ROLE_SUPPORT’)” />

<security:intercept-url pattern="/index.jsp"
access=”hasAnyRole(‘ROLE_USER’,'ROLE_iArchiveViewer_Backoffice’,'ROLE_SUPPORT’)” />

<security:intercept-url pattern="/rest/**"
access=”hasAnyRole(‘ROLE_USER’,'ROLE_iArchiveViewer_Backoffice’,'ROLE_SUPPORT’)” />

<security:intercept-url pattern="/RPCAdapter/**"
access=”hasAnyRole(‘ROLE_USER’,'ROLE_iArchiveViewer_Backoffice’,'ROLE_SUPPORT’)” />

<!–
<security:intercept-url pattern="/rest/transaction/payment/cancel/**"
access=”hasRole(‘ROLE_WEB_PROMISES_INQUIRY’)” />
–>

<!– –>

<!–
<concurrent-session-control max-sessions="1"
exception-if-maximum-exceeded=”true”/>
–>

<!– –>

<!– If you want to use basic authentication–>

<!–
Used if access is denied to the application. errorPage = the page to
display if access is denied.
–>
<bean id="accessDeniedHandler"
class=”org.springframework.security.web.access.AccessDeniedHandlerImpl”>

<bean id="authenticationProcessingFilterEntryPoint"
class=”org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint”>

<!–
============================== LDAP Definitions
======================================
–>

<!–
constructor = LDAP domain name userDn = user id used to query the
directory password = password for managerDn baseEnvironmentProperties
= additional ldap environment properties
–>
<bean id="initialDirContextFactory"
class=”org.springframework.security.ldap.DefaultSpringSecurityContextSource”>
<constructor-arg
value=”ldap://domain:389/dc=domain,dc=com” />
<property name="userDn"
value=”CN=projectldap,OU=Service Accounts,OU=Organizational Units,DC=domain,DC=com” />

follow

<!–
Used to search active directory for the user id of the user attempting
to login to the system. 1. Instead of binding directly, Acegi uses the
filter to find a matching user 2. If no user is found, that’s a
failure. 3. If a user is found, takes that users DN and tries to bind
using it 4. Success to bind means that we are okay, failure means
incorrect password constructor 0 = BaseDN for user search constructor
1 = Filter statement for user id lookup constructor 2 = initial
context factory defined above searchSubtree = search the subtrees
beneath BaseDN.
–>
<bean id="userSearch"
class=”org.springframework.security.ldap.search.FilterBasedLdapUserSearch”>

<!–
Peform the LDAP user bind using the context factory and user search

constructor = initial context factory defined above userSearch =
userSearch defined above
–>
<bean id="bindAuthenticator"
class=”org.springframework.security.ldap.authentication.BindAuthenticator”>

<!–
Used for authorization. Populate the user with a set of roles based
upon AD groups. constructor 0 = context factory constructor 1 = BaseDN
for groups groupRoleAttribute = attribute used for role names
–>
<bean id="authoritiesPopulator"
class=”org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator”>

<!–
************************** Athentication
************************************
–>

<!–
LDAP Authentication Provider uses the bindAuthenticator and
authoritiesPopulator to authenticate the user and populate the user
object with roles based upon Active Directory groups.
–>
<bean id="ldapAuthProvider"
class=”org.springframework.security.ldap.authentication.LdapAuthenticationProvider”>

<bean id="userDetailsContextMapper"
class=”com.domain.acegi.AttributesLDAPUserDetailsContextMapper”>

description

ROLE_USER
ROLE_iArchiveViewer_Backoffice
ROLE_SUPPORT

<bean id="userDetailsService"
class=”org.springframework.security.ldap.userdetails.LdapUserDetailsService”>

<security:authentication-provider
ref=”ldapAuthProvider” user-service-ref=”userService” />

Here is the Hibernate class I use to create the table for the Persistent token mechanism.

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import com.bbvacompass.domain.DataObject;

@Entity
@Table(name="PERSISTENT_LOGINS")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@org.hibernate.annotations.Entity(mutable = false)
public class PersistentLogin extends DataObject implements java.io.Serializable{

private Integer id;
private String userName;

private String series;//Primary Key
private String token;
private Timestamp lastUsed;

@Override
@Column(name="ID")
public Integer getId() {

return id;
}

@Override
public void setId(Integer id) {
this.id = id;

}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

@Id
public String getSeries() {
return series;
}

public void setSeries(String series) {
this.series = series;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}

@Column(name="LAST_USED")
public Timestamp getLastUsed() {
return lastUsed;
}

public void setLastUsed(Timestamp lastUsed) {
this.lastUsed = lastUsed;
}

}

I almost forgot one important step. You need to pass the correct paramter when a user logs in to trigger the remember me. I use a hidden field, because we didn’t want users choosing the option. It will be automatic. Remember the parameter must be _spring_security_remember_me=on or it will not work. The following code is in the login form I use.

Also, don’t forget to provide a logout button somewhere or the user will never be able to log out until the cookie expires and the above configuration is for two weeks.

Follow

Get every new post delivered to your Inbox.