Spring Security - simple Config

Last change 20/01/2013

Spring is pretty cool framework. Unfortunately for someone just starting it can be quite quite hard to get it running. This is about getting a simple spring security provider running... in a non-web way.

Spring Basics

When you look at it, spring can be quite easy to work with. All you need is a Spring context (which is the core and does most of the cool stuff) and you are ready to go. In order to get this you normally just load a ClassPathXmlApplicationContext with a reference to an xml file that configures the spring context.

When writing a non-web app, you should have the context as a static public variable - or pass it around. In a web environment this is already done for you, but when you work with your classic life cycle this has to be done manually.

public static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

The Configuration

The configuration should always start in something like the applicationContext.xml. In this file you can reference to other xml configurations to keep everything neat and seperate.<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
             http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
    >

    <import resource="classpath:securityContext.xml" />

</beans>

The Security Configuration

The security configuration defines the actual work done. Check out the spring-security documentation for more information.
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.1.xsd">


    <!-- custom service: hardocded users -->
    <security:user-service id="authenticationProvider">
        <security:user name="admin" password="test"
            authorities="ROLE_USER, ROLE_ADMIN" />
        <security:user name="user" password="test" authorities="ROLE_USER" />
    </security:user-service>

    <!--  simple active directory - uses user@domain instead of simple username
    <bean id="authenticationProvider"
          class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <constructor-arg value="mydomain.com" />
    <constructor-arg value="ldap://adserver.mydomain.com/" />
    </bean>

     -->

    <!-- simple ldap - see http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ldap.html
    <ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
    <ldap-authentication-provider id="authenticationProvider"
        user-dn-pattern="uid={0},ou=people" />
    -->
    <!-- ldap - active directory - extended sample with manager for login
        <ldap-server
        url="ldap://xyz:389"
        manager-dn="CN=xyzuer, OU=xyzAccounts, DC=xyzcom, DC=com"
        manager-password="xxxxx"
    />
        <ldap-authentication-provider
        user-search-base="OU=CorpUsers, DC=xyzcom, DC=com"
        user-search-filter="sAMAccountName={0}"
        group-search-filter="member={0}"
        group-role-attribute="cn"
        group-search-base="OU=Groups, DC=xyzcom, DC=com"
    />
        -->


    <security:authentication-manager>
        <security:authentication-provider
            user-service-ref="authenticationProvider" />
    </security:authentication-manager>


</beans:beans>

A session manager

When working with a non-web application you need to handle the sessions on your own. This is a simple class that handles sessions with login using the above configuration and also a little extra code for inactivity timeout.import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Handle authentication requests through spring security and provides a way to handle sessions
 * accross multiple requests
 *
 */
public class AuthenticationSessionManager {
    /**
     * Session timeout in milliseconds: 1h
     */
    public static final long TIMEOUT = 3600*1000;

    public static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

    private static DaoAuthenticationProvider authProvider = ctx.getBean(DaoAuthenticationProvider.class);

    private static HashMap<String, AuthenticationHolder> authMap = new HashMap<String, AuthenticationHolder>();

    public static String authenticate(String name, String password) {
        try {
            Authentication request = new UsernamePasswordAuthenticationToken(name, password);
            Authentication result = authProvider.authenticate(request);
            SecurityContextHolder.getContext().setAuthentication(result);
            String token = UUID.randomUUID().toString();
            AuthenticationHolder authHolder = new AuthenticationHolder();
            for(GrantedAuthority gauth : result.getAuthorities()){
                authHolder.authorities.add(gauth.getAuthority());
            }
            authHolder.auth = result;
            authMap.put(token, authHolder);
            return token;
          } catch(AuthenticationException e) {
              // TODO logline
            System.out.println("Authentication failed: " + e.getMessage());
            return null;
          }
    }

    /**
     * @param token the session token
     * @param role the role to search for
     * @return true if the auth connected to the token has the given role
     */
    public static boolean hasRole(String token, String role) {
        Set<String> auth = getRoles(token);
        return (auth != null && auth.contains(role));
    }

    /**
     * @param token the session token
     * @param roles the user must have ALL roles
     * @return true if the auth connected to the token has all the given role
     */
    public static boolean hasRoles(String token, String[] roles) {
        Set<String> auth = getRoles(token);
        if(auth == null) {
            return false;
        }
        for(String role : roles) {
            if(!auth.contains(role)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param token the session token
     * @param roles the user must have ANY roles
     * @return true if the auth connected to the token has any of the given role
     */
    public static boolean hasAnyRoles(String token, String[] roles) {
        Set<String> auth = getRoles(token);
        if(auth == null) {
            return false;
        }
        for(String role : roles) {
            if(auth.contains(role)) {
                return true;
            }
        }
        return false;
    }


    /**
     * helper function to get all roles
     * @param token the session token
     * @return the set of roles associated to the token
     */
    public static Set<String> getRoles(String token) {
        Set<String> auth = getByToken(token);
        if(auth == null) {
            return null;
        }
        return auth;
    }

    /**
     * @param token the session token
     * @return true if the session token is still valid
     */
    public boolean isLoggedIn(String token) {
        return getByToken(token) != null;
    }


    /**
     * get an authentication by its token.
     * Synchronized to prevent concurrency issues when working with the session hashmap
     * @param token the session token
     * @return null if no valid token is found or if it is timed out
     */
    private static synchronized Set<String> getByToken (String token) {
        AuthenticationHolder cur = authMap.get(token);
        if(cur == null) {
            return null;
        }

        // timeout reached
        if(System.currentTimeMillis() > cur.created + TIMEOUT) {
            authMap.remove(token);
            return null;
        }

        // update the created date: 1h inactivity
        cur.created = System.currentTimeMillis();

        return cur.authorities;
    }

    public static class AuthenticationHolder {
        long created = System.currentTimeMillis();
        Authentication auth;
        Set<String> authorities = new HashSet<String>();
    }
}

A test

Last but not least this is a simple text-application that uses the above stuff to do some actual work. import java.io.BufferedReader;
import java.io.InputStreamReader;


public class SpringSecurityTest {



    public static void main(String[] args) throws Exception {

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String sessionId = null;
        while(sessionId == null) {
              System.out.println("Please enter your username:");
              String name = in.readLine();
              System.out.println("Please enter your password:");
              String password = in.readLine();
              sessionId = AuthenticationSessionManager.authenticate(name, password);
        }
        System.out.println("Successfully authenticated with token "+ sessionId +". Available Roles: " +
                AuthenticationSessionManager.getRoles(sessionId));

    }
}

Site created with Corinis CCM