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));
}
}