Skip to main content

Spring Security and Regular Expression User Details Wrapper

Spring Security provides out of the box support for integrating with Single Sign On systems through its org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider object. For example you can use this to integrate with CA's SiteMinder or Oracle IDM system.

In one of my recent projects, we were integrating our custom build Java application with OracleAS 10g SSO. This SSO system as part of OracleAS integrates with Oracle Internet Directory and  injects the following header attributes for integrated partner applications.
  1. Osso-User-Dn
  2. Osso-User-Guid
  3. Osso-Subscriber
  4. Osso-Subscriber-Dn
  5. Osso-Subscriber-Guid
http://docs.oracle.com/cd/B28196_01/idmanage.1014/b15997/mod_osso.htm  provides more details on OracleAS SSO application development.  Each of these attributes have different formats. In our environment, standard was to use Oss-User-Dn which contained values in the following format cn=username, cn=Users,dc=department,dc=company,dc=com.

Our integrated application had an internal user database as well. We needed to match the pre-authenticated header value with the username stored in our internal application database. In order to achieve this, we needed to parse the incoming header value and grab the "username". One way to achieve this is to modify your UserDetailsService.loadByUsername custom implementation where you can parse the incoming username parameter and pass that to your access queries. This obviously is not very usable and specific to the format of the incoming parameter value.

To generalize the approach and create a re-usable bean configuration in Spring Security, we ended up created a bean called RegexUserDetailsWrapper. This bean wraps our original UserDetailsService implementation calling our data store with the actual username. RegexUserDetailsWrapper is used to configure a regular expression parsing the incoming pre-authentication header value and then internally calling our original UserDetailsService implementation. Here is the code for the RegexUserDetailsWrapper.

package com.company.utils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class RegexUserDetailsWrapper implements UserDetailsService, InitializingBean {

    private String regex;
    private int group = 0;
    private UserDetailsService userDetailsService;
    private Log logger = LogFactory.getLog(UserContext.class);

    @Override
    public void afterPropertiesSet() throws Exception {
        if (StringUtils.isEmpty(regex) || userDetailsService == null)
            throw new Exception("regex or userDetailsService must be provided");
    }

    public RegexUserDetailsWrapper() {
    }

    public RegexUserDetailsWrapper(String regex, int group, UserDetailsService userDetailsService) {
        this.regex = regex;
        this.group = group;
        this.userDetailsService = userDetailsService;
    }

    public String getRegex() {
        return regex;
    }

    public void setRegex(String regex) {
        this.regex = regex;
    }

    public int getGroup() {
        return group;
    }

    public void setGroup(int group) {
        this.group = group;
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    public UserDetails loadUserByUsername(String complexUsername) throws UsernameNotFoundException, DataAccessException {
        String username = null;
        try {
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(complexUsername);
            while (matcher.find()) {
                username = matcher.group(group);
                break;
            }
        } catch (PatternSyntaxException e) {
            logger.error(e.getMessage(), e);
        }
        logger.debug("Matching group username:" + username);

        return this.userDetailsService.loadUserByUsername(username);
    }
}

To configure this bean you can use the following Spring configuration.
<beans:bean id="preauthAuthProvider"
        class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <beans:property name="preAuthenticatedUserDetailsService"
            ref="userDetailsServiceWrapper" />
    </beans:bean>

    <beans:bean id="userDetailsServiceWrapper"
        class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
        <beans:property name="userDetailsService" ref="regexUserDetailsService" />
    </beans:bean>
    
    <beans:bean id="regexUserDetailsService" class="com.company.utils.RegexUserDetailsWrapper">
        <beans:property name="regex" value="cn=(.+),cn=users,dc=department,dc=company,dc=com"/>
        <beans:property name="group" value="1"/>
        <beans:property name="userDetailsService" ref="originalUserDetailsService" />
    </beans:bean>

As you can see regexUserDetailsService provides you with a configurable regular expression, regular expression group to match and the original userDetailsService implementation. This code and configuration ensures any incoming parameter can be configured to match any user identifier in a given local storage whether it is LDAP or RDMS based.

It would be nicer if this was part of the Spring Security suite.

Comments