There are situations where you want to use Spring Security for authorization, but the user has already been reliably authenticated by some external system prior to accessing the application. In such situations where Spring Security Pre-authentication comes into picture we refer to these situations as “pre-authenticated” scenarios. Examples include X.509, Siteminder and authentication by the J2EE container in which the application is running. When using spring security pre-authentication, Spring Security has to

Identify the user making the request

Obtain the authorities for the user

The details will depend on the external authentication mechanism. A user might be identified by their certificate information in the case of X.509, or by an HTTP request header in the case of Siteminder. If relying on container authentication, the user will be identified by calling the getUserPrincipal() method on the incoming HTTP request. In some cases, the external mechanism may supply role/authority information for the user but in others the authorities must be obtained from a separate source, such as a UserDetailsService.

You can find details here http://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html

In this example, it is assumed that the user has been authenticated using Siteminder SSO credentials and user no longer needs to be authenticated but has to be authorized only to perform the activities using REST URL.

Prerequisites

The following configurations are required in order to run the application

Eclipse Kepler
JDK 1.8
Tomcat 8
Have maven 3 installed and configured
Spring mvc 4 dependencies in pom.xml
Spring security 4 dependencies in pom.xml

1. Please find the below pom.xml file

We have added dependencies for sprin mvc, security, orm and apart from spring dependencies, we need mysql-connector because we will use MySQL database for this example. We have added servlet and jsp APIs in order to support for jsp pages.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.jeejava</groupId>
	<artifactId>spring-security-preauth</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-security-preauth Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<java.version>1.8</java.version>
		<jackson.version>2.5.3</jackson.version>
		<spring.version>4.3.5.RELEASE</spring.version>
		<mysqlconnector.version>5.1.36</mysqlconnector.version>
		<spring.security.version>4.2.1.RELEASE</spring.security.version>
	</properties>

	<dependencies>
		<!-- Spring mvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring ORM -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- mysql java connector -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysqlconnector.version}</version>
		</dependency>

		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
			<scope>provided</scope>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>spring-security-preauth</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

In the above pom.xml file you notice additional plugin for failOnMissingWebXml configuration is required because we are going to write Java code purely based on annotations, so we do not need the web.xml file in WEB-INF directory.

The jackson dependency is required to map output in JSON response automatically using extendMessageConverters() method in WebMvcConfig class.

2. Find below jdbc.properties file under src/main/resources directory. In the following jdbc.properties file we have defined database details as key/value pairs. You may use other database but you have to update with your own database details and also you have to change the dependency mysql-connector as per your database vendor.

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/jeejava
jdbc.username=root
jdbc.password=

3. We need this below class in order to initialize annotation based security configurations.

package com.jeejava.spring.security.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}

The above class is equivalent to add the filter declaration DelegatingFilterProxy to your web.xml file.

This provides a hook into the Spring Security web infrastructure. DelegatingFilterProxy is a Spring Framework class which delegates to a filter implementation which is defined as a Spring bean in your application context. In this case, the bean is named springSecurityFilterChain, which is an internal infrastructure bean created by the namespace to handle web security. Note that you should not use this bean name yourself.

4. We need below security configuration using Java annotation in order to authorization. The below WebSecurityConfig class, which is responsible for all security configurations, extends WebSecurityConfigurerAdapter and overrides configure() and authenticationManager() methods. We have overridden configure(WebSecurity web) to ignore security for static resources such as js, css, images etc. We have overridden method configure(HttpSecurity http) to apply security for all URLs including the URL which is having pattern /blogs with roles as ADMIN.

We have also defined some methods to return objects as spring beans for authentication purpose. We have assumed here that the user has already been authenticated using SiteMinder authentication and that’s why we are just checking for the http header SM_USER exists or not for further authentication.

package com.jeejava.spring.security.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;

import com.jeejava.spring.security.service.CustomUserDetailsService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/static/**");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.addFilterAfter(siteminderFilter(), RequestHeaderAuthenticationFilter.class).authorizeRequests()
				.antMatchers("/").permitAll().anyRequest().authenticated().antMatchers("/blogs")
				.access("hasRole('ADMIN')");
	}

	@Bean(name = "siteminderFilter")
	public RequestHeaderAuthenticationFilter siteminderFilter() throws Exception {
		RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
		requestHeaderAuthenticationFilter.setPrincipalRequestHeader("SM_USER");
		requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
		return requestHeaderAuthenticationFilter;
	}

	@Bean
	@Override
	protected AuthenticationManager authenticationManager() throws Exception {
		final List<AuthenticationProvider> providers = new ArrayList<>(1);
		providers.add(preauthAuthProvider());
		return new ProviderManager(providers);
	}

	@Bean(name = "preAuthProvider")
	PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
		PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
		provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
		return provider;
	}

	@Bean
	UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper() throws Exception {
		UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = new UserDetailsByNameServiceWrapper<>();
		wrapper.setUserDetailsService(customUserDetailsService);
		return wrapper;
	}

}

5. Next class WebMvcConfig is responsible for configuring spring mvc related stuffs. This class extends WebMvcConfigurerAdapter and overrides the method extendMessageConverters() to send the response as JSON object. We have defined JdbcTemplate bean for quering MySQL database using spring’s JdbcTemplate API.

package com.jeejava.spring.mvc.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

@EnableWebMvc
@Configuration
@PropertySource(value = { "classpath:jdbc.properties" })
@ComponentScan(basePackages = "com.jeejava.spring.*")
public class WebMvcConfig extends WebMvcConfigurerAdapter {

	@Autowired
	private Environment environment;

	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		converters.add(mappingJackson2HttpMessageConverter());
	}

	@Bean
	public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
		MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
		converter.setObjectMapper(
				new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
		return converter;
	}

	@Bean
	public JdbcTemplate getJdbcTemplate() throws ClassNotFoundException {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
		dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
		dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
		dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		return jdbcTemplate;
	}

}

The annotation @EnableWebMvc is equivalent to <mvc:annotation-driven /> to work with annotations in Spring MVC.

The annotation @ComponentScan is equivalent to <context:component-scan/> to load all annotation-driven controllers from the given base package.

@PropertySource loads property files from resource directory.

6. Now load web mvc and security configuration when application starts up. We have created the following class MvcWebApplicationInitializer in order to define the deployment descriptor and initialize the servlet as we are not using web.xml file here.

package com.jeejava.spring.mvc.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.jeejava.spring.mvc.config.WebMvcConfig;
import com.jeejava.spring.security.config.WebSecurityConfig;

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

}

The above class declared Spring MVC DispatcherServlet, that acts as a front controller to handle incoming request and response for the URL pattern "/". This is equivalent to declaring DispatcherServlet in web.xml file.

7. Create below User class to represent table row.

package com.jeejava.spring.model;

public class User {

	private String username;
	private String password;
	private String role;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getRole() {
		return role;
	}

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

}

8. Create below class in order to map table row with User class. We use here spring’s RowMapper interface to define our own process for mapping database row Java class object.

package com.jeejava.spring.mapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.jeejava.spring.model.User;

public class UserRowMapper implements RowMapper<User> {

	@Override
	public User mapRow(ResultSet rs, int row) throws SQLException {
		User userDetails = new User();
		userDetails.setUsername(rs.getString("user_name"));
		userDetails.setPassword(rs.getString("user_pass"));
		userDetails.setRole(rs.getString("user_role"));
		return userDetails;
	}

}

9. Create DAO layer to retrieve data from table

package com.jeejava.spring.dao;

import com.jeejava.spring.model.User;

public interface UserDao {

	public User getUser(String username);

}
package com.jeejava.spring.dao.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.jeejava.spring.dao.UserDao;
import com.jeejava.spring.mapper.UserRowMapper;
import com.jeejava.spring.model.User;

@Repository
@Transactional
public class UserDaoImpl implements UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public User getUser(String username) {
		try {
			final String sql = "select u.user_name user_name, u.user_pass user_pass, ur.user_role user_role from user u, user_role ur where u.user_name = ? and u.user_name = ur.user_name";
			User userDetails = jdbcTemplate.queryForObject(sql, new Object[] { username }, new UserRowMapper());
			return userDetails;
		} catch (EmptyResultDataAccessException ex) {
			return null;// should have proper handling of Exception
		}
	}

}

In the above we have annotated the DAO class with @Repository to segregate the layer and we are also applying spring’s @Transactional annotation for rolling back the failed operations in database. @Transactional is not required for reading the database but required for writing/modifying data into the database. We have used here spring’s JdbcTemplate to query the database.

The corresponding database tables in MySQL database is given below. We have created two tables user and user_role and mapping between these two tables in MySQL database.

USE `jeejava`;

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `user_name` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
  `user_pass` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `enable` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `user` */

insert  into `user`(`user_name`,`user_pass`,`enable`) values ('roy','ae685575101ee7165c90a8f2c30c6e60cdd9e482',1);

/*Table structure for table `user_role` */

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `user_name` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
  `user_role` varchar(15) COLLATE utf8_unicode_ci NOT NULL,
  KEY `fk_user` (`user_name`),
  CONSTRAINT `fk_user` FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `user_role` */

insert  into `user_role`(`user_name`,`user_role`) values ('roy','ROLE_ADMIN');

10. Create service layer code to process business logic. In the below service layer we are using spring’s UserDetails, UserDetailsService, GrantedAuthority to handle authentication by spring itself and we are just letting spring know what data spring has to use, nothing else.

package com.jeejava.spring.security.service;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.jeejava.spring.dao.UserDao;
import com.jeejava.spring.model.User;

@Service
public class CustomUserDetailsService implements UserDetailsService {

	@Autowired
	private UserDao userDao;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.getUser(username);
		if (user == null) {// should have proper handling of Exception
			throw new UsernameNotFoundException("User '" + username + "' not found.");
		}
		GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
		UserDetails details = new org.springframework.security.core.userdetails.User(user.getUsername(),
				user.getPassword(), Arrays.asList(grantedAuthority));
		return details;
	}

}

11. Create REST controller class using Spring framework. We have exposed two endpoints in the spring rest controller – one is with base URL and another one with pattern /blogs. So when you hit the base URL with header SM_USER, you should not be asked for credentials but when you hit the URL that is having /blogs with SM_USER, you should have role as ADMIN otherwise you will get Access denied – 403 error.

package com.jeejava.spring.security.controllers;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.jeejava.spring.dto.BlogDto;

@RestController
public class SpringSecurityController {

	@ResponseBody
	@RequestMapping("/")
	public String defaultPage(Model model) {
		return "Welcome to Spring Security PreAuthentication";
	}

	@ResponseBody
	@RequestMapping("/blogs")
	public List<BlogDto> getAllBlogs(Model model) {
		return getBlogs();
	}

	private List<BlogDto> getBlogs() {
		List<BlogDto> blogs = new ArrayList<>();

		BlogDto b1 = new BlogDto();
		b1.setTitle("Spring Security using XML");
		b1.setAuthor("http://roytuts.com");
		b1.setText("Spring Security. Autherntication. Authorization.");
		b1.setDate(new Date());

		BlogDto b2 = new BlogDto();
		b2.setTitle("Spring Security using Annotation");
		b2.setAuthor("http://roytuts.com");
		b2.setText("Spring Security. Autherntication. Authorization.");
		b2.setDate(new Date());

		BlogDto b3 = new BlogDto();
		b3.setTitle("Spring Security using UserDetailsService");
		b3.setAuthor("http://roytuts.com");
		b3.setText("Spring Security. Autherntication. Authorization.");
		b3.setDate(new Date());

		BlogDto b4 = new BlogDto();
		b4.setTitle("Spring MVC using XML");
		b4.setAuthor("http://roytuts.com");
		b4.setText("Spring Model-View-Controller.");
		b4.setDate(new Date());

		BlogDto b5 = new BlogDto();
		b5.setTitle("Spring MVC using Annotation");
		b5.setAuthor("http://roytuts.com");
		b5.setText("Spring Model-View-Controller.");
		b5.setDate(new Date());

		blogs.add(b1);
		blogs.add(b2);
		blogs.add(b3);
		blogs.add(b4);
		blogs.add(b5);

		return blogs;
	}

}

In the above class we have also defined one private method to declare some dummy data fr testing purpose.

The DTO class is given below

package com.jeejava.spring.dto;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class BlogDto {

	private String title;
	private String author;
	@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
	private Date date;
	private String text;

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

}

12. Deploy the application in Tomcat 8 server

Now when you try to access the URL http://localhost:8080/spring-security-preauth/ without setting SM_USER header, you will see below exception

spring security pre-authentication

When you access URL http://localhost:8080/spring-security-preauth by setting SM_USER:roytuts.com at header

spring security pre-authentication

When you access URL http://localhost:8080/spring-security-preauth/blogs by setting SM_USER:roytuts.com at header

spring security pre-authentication

You see above error because the above URL is accessible only for those who have role ADMIN. Now set SM_USER:roy and access the same URL.

spring security pre-authentication

The application does not require you to authenticate because it is assumed that the user is already authenticated using Siteminder authentication. So the application requires only authorization using SM_USER header.

Thanks for reading.

Tags:

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on Roy Tutorials | TwitterFacebook Google PlusLinkedin | Reddit

2 thoughts on “Spring Security Pre-authentication Example

  1. Thanks for this post. I was struggling to implement a solution for SSO with Siteminder and your post helped me a lot. I do have a question though, how can I check for and re-authenticate when SMSESSION expires?

Leave a Reply

Your email address will not be published. Required fields are marked *