5 minutes with – Spring Authentication and Authorization Service (JAAS)

In the large numbers of choices of how build an authentication and authorization service, Java community has defined a stardard of it called Jaas.

In this article I’d like to show you the integration of this standard with Spring framework. So, I’m going to show you how the two different layers of authentication and authorization are both integrated and used. As usual, we’ll see that by an example.

For the purpose of the article, I used an easy example to simulate a back-office system. I created two profiles, Administrator and Customer, who can access only in their private area.

Before going on, take a look at some notes. You can find an official reference of Jaas Technology at this link http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html.

Let’s start with the example. I’m going to create a website with the follow navigation

jaas1

As you can see, to access at Administrator’s or Customer’s page you must be logged at the system. I put these behaviour into the applicationContext.xml

<?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:sec="http://www.springframework.org/schema/security"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">

	<sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/private/admin/**" access="hasRole('ADMIN')" />
		<sec:intercept-url pattern="/private/customer/**" access="hasRole('CUSTOMER')" />
		<sec:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=1" />
		<sec:logout logout-success-url="/home.jsp" logout-url="/j_spring_security_logout" />
	</sec:http>

	<sec:authentication-manager>
		<sec:authentication-provider ref="jaasAuthProvider" />
	</sec:authentication-manager>

	<bean id="jaasAuthProvider"
		class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
		<property name="configuration">
			<bean
				class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
				<constructor-arg>
					<map>
						<entry key="SPRINGSECURITY">
							<array>
								<bean class="javax.security.auth.login.AppConfigurationEntry">
									<constructor-arg value="it.springwebjaas.Login" />
									<constructor-arg>
										<util:constant
											static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
									</constructor-arg>
									<constructor-arg>
										<map></map>
									</constructor-arg>
								</bean>
							</array>
						</entry>
					</map>
				</constructor-arg>
			</bean>
		</property>
		<property name="authorityGranters">
			<list>
				<bean class="it.springwebjaas.RoleGranter" />
			</list>
		</property>
	</bean>
</beans>

So, just few notes for the above code. In <sec> section I’ve defined the security policies for this website. The jaasAuthProvider is the integration between Spring and Jaas technology. In the constructor arguments are configurated the Login class it.springwebjaas.Login and the authority granters it.springwebjaas.RoleGranter. Briefly, once the users are  authenticated, they go to RoleGranted to get the roles.

Take a look at Login class.

package it.springwebjaas;

import it.springwebjaas.principal.UserPrincipal;
import it.springwebjaas.security.EncodeDecode;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class Login implements LoginModule {

	private String password;
	private String username;
	private Subject subject;

	public boolean login() throws LoginException {

		// Check the password against the username "admin" or "customer"
		if (username == null
				|| (!username.equals("admin") && !username.equals("customer"))) {
			throw new LoginException("User not valid");
		}

		// Check password valid
		String passwordDecrypted = EncodeDecode.Decrypt(password);
		String[] passwordSplitted = null;

		// Split at "_"
		if (passwordDecrypted != null)
			passwordSplitted = passwordDecrypted.split("_");
		else
			throw new LoginException("Password not valid");

		if (passwordSplitted.length == 2) {

			// Check the password and validity of that.
			if (passwordSplitted[0].equals(EncodeDecode.ADMIN_PASSWORD) && (isDateRangeValid(passwordSplitted[1]))) {
				subject.getPrincipals().add(new UserPrincipal(username));
				return true;
			} else
				throw new LoginException("Password not valid");
		} else
			throw new LoginException("Password not valid");
	}

	private boolean isDateRangeValid(String passwordDate) throws LoginException {
		// Check the time validity
		SimpleDateFormat dateFormat = new SimpleDateFormat(
				"yyyy-MM-dd HH-mm-ss");

		try {
			Date convertedDate = dateFormat.parse(passwordDate);

			long timePasswordDifference = (new java.util.Date().getTime() - convertedDate
					.getTime()) / (60 * 1000) % 60;

			if (timePasswordDifference < EncodeDecode.MINUTE_PASSWORD_EXPIRES)
				return true;
			else
				return false;

		} catch (ParseException e) {
			// TODO Auto-generated catch block
			throw new LoginException("User not valid");
		}
	}

	@Override
	public boolean abort() throws LoginException {return false;}

	@Override
	public boolean commit() throws LoginException {return true;}

	@Override
	public boolean logout() throws LoginException {return false;}

	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map<String, ?> state, Map<String, ?> options) {
		this.subject = subject;

		try {
			NameCallback nameCallback = new NameCallback("prompt");
			PasswordCallback passwordCallback = new PasswordCallback("prompt",
					false);

			callbackHandler.handle(new Callback[] { nameCallback,
					passwordCallback });

			password = new String(passwordCallback.getPassword());
			username = nameCallback.getName();

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

To get more funny this example I didn’t neither put the passwords inside the code nor put them into some configuration file. I used an easy password generator on base64 that I found on internet (I’m sorry but I don’t remember where I’ve found it).

The Encoder and Decoder code.

package it.springwebjaas.security;

import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;

@SuppressWarnings("restriction")
public class EncodeDecode {

	// password for encryption
	final static String keyPassword = "password12345678";
	// put this as key in AES
	static SecretKeySpec key = new SecretKeySpec(keyPassword.getBytes(), "AES");

	public final static String ADMIN_PASSWORD = "admin";
	public final static int MINUTE_PASSWORD_EXPIRES = 2;

	public static String Encrypt(String input) {

		try {
			// Parameter specific algorithm
			AlgorithmParameterSpec paramSpec = new IvParameterSpec(
					keyPassword.getBytes());
			// Whatever you want to encrypt/decrypt
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			// You can use ENCRYPT_MODE (ENCRYPTunderscoreMODE) or DECRYPT_MODE
			// (DECRYPT underscore MODE)
			cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
			// encrypt data
			byte[] ecrypted = cipher.doFinal(input.getBytes());
			// encode data using standard encoder
			@SuppressWarnings("restriction")
			String output = new BASE64Encoder().encode(ecrypted);
			return output;
		} catch (Exception e) {
			return null;
		}

	}
	public static String Decrypt(String input) {
		try {
			AlgorithmParameterSpec paramSpec = new IvParameterSpec(
					keyPassword.getBytes());
			// Whatever you want to encrypt/decrypt using AES /CBC padding
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			// You can use ENCRYPT_MODE or DECRYPT_MODE
			cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
			// decode data using standard decoder
			@SuppressWarnings("restriction")
			byte[] output = new BASE64Decoder().decodeBuffer(input);
			// Decrypt the data
			byte[] decrypted = cipher.doFinal(output);

			return new String(decrypted);

		} catch (Exception e) {
			return null;
		}
	}
}

The RoleGranter class (I know what you could say….come on, it’s only an example!).

package it.springwebjaas;

import java.security.Principal;
import java.util.Collections;
import java.util.Set;

import org.springframework.security.authentication.jaas.AuthorityGranter;

public class RoleGranter implements AuthorityGranter {

    public Set<String> grant(Principal principal) {

    	if (principal.getName().equals("admin"))
    		return Collections.singleton("ADMIN");
    	else
    		return Collections.singleton("CUSTOMER");
    }
}

The password generator class.

package it.springwebjaas.client;

import it.springwebjaas.security.EncodeDecode;
import java.text.SimpleDateFormat;

public class PasswordGenerator {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");

		String encrypt = EncodeDecode.Encrypt(EncodeDecode.ADMIN_PASSWORD + "_" + formatter.format(new java.util.Date()));
		System.out.println(encrypt);

		String decrypt = EncodeDecode.Decrypt(encrypt);
		System.out.println(decrypt);
	}
}

How does it work? The password is valid for two minutes, after this time it expires.

Ok, it’s good for an example but I’m not talking about a strong password so, please, don’t use it on the production environment.

The login page is the follow:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Login Page</title>
<style>
.errorblock {
	color: #ff0000;
	background-color: #ffEEEE;
	border: 3px solid #ff0000;
	padding: 8px;
	margin: 16px;
}

body {
	font-family: Tahoma;
	font-size: 12px
}
td {
	font-family: Tahoma;
	font-size: 12px
}
h3 {
	font-family: Tahoma;
	font-size: 12px
}
h2 {
	font-family: Tahoma;
	font-size: 10px
}
input {
	font-family: Tahoma;
	font-size: 12px
}
</style>
</head>
<body onload='document.f.j_username.focus();'>
	<h3>Order WareHouse Dashboard</h3>
	<h2>Please insert your credentials.</h2>
	<c:if test="${not empty param.error}">
		<div class="errorblock">
			Your login attempt was not successful, try again.<br /> Caused :
			${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
		</div>
	</c:if>

	<form name='f' action="<c:url value='j_spring_security_check' />"
		method='POST'>

		<table>
			<tr>
				<td>User:</td>
				<td><input type='text' name='j_username' value=''></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input type='password' name='j_password' /></td>
			</tr>
			<tr>
				<td colspan='2'><input name="submit" type="submit" value="Send" /></td>
			</tr>
		</table>

	</form>
</body>
</html>

The administrator private page (the customer private page it’s the same of that).

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<style>
body {
	font-family: Tahoma;
	font-size: 12px
}
</style>
</head>
<body>
	<h1>Administrator Authenticated</h1>
	<b>UserName</b>: ${userPrincipal.name}<br>
	<b>Role</b>: ${userPrincipal.role}<br>
	<b>Login Date</b>: ${userPrincipal.loginTime}<br>
	<a href="<c:url value="/j_spring_security_logout" />" > Logout</a>
</body>
</html>

I used Spring Mvc to manage the private page. This is the configuration and the controller file

<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Use @Component annotations for bean definitions -->
	<context:component-scan base-package="it.springwebjaas.controller" />

	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix">
			<value>/WEB-INF/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean>
</beans>
package it.springwebjaas.controller;

import it.springwebjaas.principal.UserPrincipal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.security.authentication.jaas.JaasGrantedAuthority;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

@Controller
public class SecureController {

	@RequestMapping(value="/admin/index")
	//public String getOrder(ModelMap model, java.security.Principal principal) {
	public String getAdmin(ModelMap model) {

		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		JaasGrantedAuthority jaasGrantedAuthority = (JaasGrantedAuthority)(auth.getAuthorities().toArray()[0]);

		UserPrincipal userPrincipal = (UserPrincipal)jaasGrantedAuthority.getPrincipal();
		userPrincipal.setRole(jaasGrantedAuthority.getAuthority());

		model.addAttribute("userPrincipal", userPrincipal);
		return "admin/index";
	}

	@RequestMapping(value="/customer/index")
	//public String getOrder(ModelMap model, java.security.Principal principal) {
	public String getCustomer(ModelMap model) {

		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		JaasGrantedAuthority jaasGrantedAuthority = (JaasGrantedAuthority)(auth.getAuthorities().toArray()[0]);

		UserPrincipal userPrincipal = (UserPrincipal)jaasGrantedAuthority.getPrincipal();
		userPrincipal.setRole(jaasGrantedAuthority.getAuthority());

		model.addAttribute("userPrincipal", userPrincipal);
		return "customer/index";
	}
}

I built a custom Principal that hold information as: username, last login date, role.

package it.springwebjaas.principal;

import java.security.Principal;
import java.util.Date;

public class UserPrincipal implements Principal, java.io.Serializable  {

	private String name 	= null;
	private Date loginTime 	= null;
	private String role		= null;

	public UserPrincipal(String name) {
		if (name == null)
		    throw new NullPointerException("illegal null input");
		this.name = name;
		this.loginTime = new Date();
	}

	@Override
	public String getName() {
		return name;
	}

	public Date getLoginTime() {
		return loginTime;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}
}

The last point is the web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	version="2.5">
	<display-name>JAAS Sample Application</display-name>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<filter>
		<filter-name>localizationFilter</filter-name>
		<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
	</filter>
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>localizationFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<welcome-file-list>
		<welcome-file>home.jsp</welcome-file>
	</welcome-file-list>
	<servlet>
		<servlet-name>springwebjaas</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>springwebjaas</servlet-name>
		<url-pattern>/private/*</url-pattern>
	</servlet-mapping>
</web-app>

It’s time to launch the application. If you browse at /private/admin/index you’ll be redirect at LoginPage. So, generate a password and put it inside password box with admin username. On the other hand, the page /private/customer/index gives you “access is denied”.

The maven references for this project.

<dependencies>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-asm</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-aop</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-beans</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-core</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-expression</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-orm</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-acl</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-config</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-core</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-crypto</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-taglibs</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.security</groupId>
  		<artifactId>spring-security-web</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-tx</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-web</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-webmvc</artifactId>
  		<version>3.1.0.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>taglibs</groupId>
  		<artifactId>standard</artifactId>
  		<version>1.1.2</version>
  	</dependency>
  	<dependency>
  		<groupId>commons-logging</groupId>
  		<artifactId>commons-logging</artifactId>
  		<version>1.1.1</version>
  	</dependency>
  	<dependency>
  		<groupId>jstl</groupId>
  		<artifactId>jstl</artifactId>
  		<version>1.1.2</version>
  	</dependency>
  	<dependency>
  		<groupId>javax.servlet</groupId>
  		<artifactId>servlet-api</artifactId>
  		<version>2.5</version>
  	</dependency>
  </dependencies>

Source code: SpringWebJaas

Advertisements

6 thoughts on “5 minutes with – Spring Authentication and Authorization Service (JAAS)

  1. Congratulations, Marco Ghisellini!
    I loved your article, because it´s very useful!
    Could you, please, make the entire source code available for download?

    • Thanks Aliel.
      I would like to do it but I can’t attach a compressed files to share the source code as one archive at WordPress. Anyway, I posted every codes in the article, so you can build the solution. However, if you want a source code in compressed format, give me your email and I’ll send you the compressed archive .

    • Thanks Steve,
      Yes, you are right, I didn’t upload the source code.
      Anyway I’ve uploaded the code and you can find out the link at the end of the article. The file is with “doc” extension due to wordpress limitation. So, you’ve to rename it as “zip” archive.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s