Spring Boot Security Pre-authentication Example

Spring Security Pre Authentication

Here you will see an example on Spring Security Pre-authentication. 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 I refer to these situations as “pre-authenticated” scenarios.

Examples of pre-authentication may 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.

Related Post:

Assumption

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

Java 1.8+, Spring Boot 2.1.5/2.7.5, Gradle 4.10.2, Maven 3.8.5

Project Setup

Create a maven or gradle based project in your favorite IDE or tool. you can use either pom.xml file or build.gradle script.

I will add the required dependencies for our Spring Security Pre-authentication example.

I have added dependencies for spring security, spring jdbc, I am using here in-memory h2 and MySQL databases. You can use any database as per your choice.

POM File

You can use the following pom.xml file for your project:

<?xml version="1.0" encoding="UTF-8"?>

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-security-pre-authentication</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.5</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!--<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>test</scope>
		</dependency>-->
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Build Script

For gradle based project you can use the following build.gradle script.

buildscript {
	ext {
		springBootVersion = '2.1.5.RELEASE'
	}
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
	mavenLocal()
    mavenCentral()
}
dependencies {
	compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	compile("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
	compile("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")
	runtime("com.h2database:h2:1.4.196")
}

SQL Scripts

MySQL Database

For MySQL server you may use the following SQL script:

USE `roytuts`;

/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `user_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
  `user_pass` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `enable` tinyint NOT NULL DEFAULT '1',
  PRIMARY KEY (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*Data for the table `user` */
insert  into `user`(`user_name`,`user_pass`,`enable`) values ('roy','$2a$12$PN0MjtyNGWW.AjWdIuYKxe4.4Grjs4K7oanuAnt/WSDSvFlUc3eQi',1);

/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
  `user_role` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL,
  KEY `fk_user` (`user_name`),
  CONSTRAINT `fk_user` FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*Data for the table `user_role` */
insert  into `user_role`(`user_name`,`user_role`) values ('roy','ROLE_ADMIN');

H2 Database

I am creating SQL scripts in order to create tables in h2 in-memory database under src/main/resources directory. I will create two SQL scripts – one for creating tables and another for inserting data into the tables.

Table Script

I have created two table to map role with a user.

CREATE TABLE `user` (
  `user_name` varchar(30) NOT NULL,
  `user_pass` varchar(255) NOT NULL,
  `enable` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`user_name`)
);
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_name` varchar(30) NOT NULL,
  `user_role` varchar(15) NOT NULL,
  FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`)
);

Insert Data Script

I will insert only one user to show this example.

insert  into `user`(`user_name`,`user_pass`,`enable`) values ('roy','$2a$12$PN0MjtyNGWW.AjWdIuYKxe4.4Grjs4K7oanuAnt/WSDSvFlUc3eQi',1);
insert  into `user_role`(`user_name`,`user_role`) values ('roy','ROLE_ADMIN');

The above scripts will be executed when the application will be started using Spring Boot main class.

DataSource Configuration

I have created below Spring Configuration class to define DataSource and JdbcTemplate beans. You can use either MySQL or H2 datasource configuration depending upon your use of database.

@Configuration
public class SpringPreAuthenticationConfig {
	
	@Autowired
	private Environment environment;

	/*@Bean
	public DataSource dataSource() {
		EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
		return embeddedDatabaseBuilder.addScript("classpath:create-table.sql").addScript("insert-data.sql")
				.setType(EmbeddedDatabaseType.H2).build();
	}*/
	
	@Bean
	public DriverManagerDataSource dataSource() {
		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"));
		return dataSource;
	}

	@Bean
	public JdbcTemplate jdbcTemplate(DataSource dataSource) {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		return jdbcTemplate;
	}

}

In the above class I have used Spring JDBC API, you can also use Spring Data JPA API to perform database operations.

Remember if you use MySQL datasource then you need the following configuration into application.properties file under src/main/resources folder.

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

Spring Security Configuration

The security configuration class using Java annotation based configuration in order to implement the authorization. The below WebSecurityConfig class, which is responsible for all security configurations, and since Spring Boot 2.7 your class does not need to extend WebSecurityConfigurerAdapter and creates two methods with functionality filterChain(HttpSecurity http) and authenticationManager() methods. I have also ignored security for static resources such as js, css, images etc. I have applied security for all URLs including the URL which is having pattern /blogs with roles as ADMIN.

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

@Configuration
@EnableWebSecurity
public class PreAuthenticationSecurityConfig {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	@Bean
	protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.addFilterAfter(siteminderFilter(), RequestHeaderAuthenticationFilter.class).authorizeRequests()
				.antMatchers("/static/**").permitAll().antMatchers("/").permitAll().antMatchers("/blogs")
				.access("hasRole('ADMIN')").anyRequest().authenticated();

		return http.build();
	}

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

	@Bean
	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;
	}

}

Model Class

I need to map database table columns to Java attributes, so I will create User class to represent it.

public class User {
	private String username;
	private String password;
	private String role;
	
        //getters and setters
}

Row Mapper Class

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

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

Spring DAO

I need DAO or Repository class to perform database operations.

I have used here Spring JDBC API to perform our database operation. If you want you can use Spring Data JPA API to perform database activities. I am using Spring JdbcTemplate to query our database.

@Repository
public class UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	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 UserRowMapper(), username);
			return userDetails;
		} catch (EmptyResultDataAccessException ex) {
			return null;// should have proper handling of Exception
		}
	}

}

In the above I have annotated the DAO class with @Repository to segregate the layer for DAO. I have used here spring’s JdbcTemplate to query the database.

VO Class

I will create DTO or VO class to return as a response object in JSON format to the end users or clients. It’s not a good idea to use Entity or Model classes to send as a response object to end users.

The below class represents a blog object, which will be finally returned in JSON format.

public class BlogVo {
	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;
	
        //getters and setters
}

Spring Service

Create service layer code to process business logic.

In the below service layer I am using spring’s UserDetails, UserDetailsService, GrantedAuthority to handle authentication by spring itself and I am just letting spring know what data spring has to use, nothing else.

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

Spring REST Controller

Create REST controller class using Spring framework. I 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.

@RestController
public class UserRestController {
	@GetMapping("/")
	public ResponseEntity<String> defaultPage(Model model) {
		return new ResponseEntity<String>("Welcome to Spring Security PreAuthentication", HttpStatus.OK);
	}
	@GetMapping("/blogs")
	public ResponseEntity<List<BlogVo>> getAllBlogs(Model model) {
		return new ResponseEntity<List<BlogVo>>(getBlogs(), HttpStatus.OK);
	}
	private List<BlogVo> getBlogs() {
		List<BlogVo> blogs = new ArrayList<>();
		BlogVo b1 = new BlogVo();
		b1.setTitle("Spring Security using XML");
		b1.setAuthor("https://roytuts.com");
		b1.setText("Spring Security. Autherntication. Authorization.");
		b1.setDate(new Date());
		BlogVo b2 = new BlogVo();
		b2.setTitle("Spring Security using Annotation");
		b2.setAuthor("https://roytuts.com");
		b2.setText("Spring Security. Autherntication. Authorization.");
		b2.setDate(new Date());
		BlogVo b3 = new BlogVo();
		b3.setTitle("Spring Security using UserDetailsService");
		b3.setAuthor("https://roytuts.com");
		b3.setText("Spring Security. Autherntication. Authorization.");
		b3.setDate(new Date());
		BlogVo b4 = new BlogVo();
		b4.setTitle("Spring MVC using XML");
		b4.setAuthor("https://roytuts.com");
		b4.setText("Spring Model-View-Controller.");
		b4.setDate(new Date());
		BlogVo b5 = new BlogVo();
		b5.setTitle("Spring MVC using Annotation");
		b5.setAuthor("https://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, I have also defined one private method to declare some dummy data for testing purpose.

Spring Boot Main Class

Spring Boot application created as standalone application and can be easily deployed into embedded Tomcat server using main class.

@SpringBootApplication
public class SpringPreAuthentication {
	public static void main(String[] args) {
		SpringApplication.run(SpringPreAuthentication.class, args);
	}
}

Testing Spring Security Pre-Authentication

Run the main class to deploy your application into Tomcat server.

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

spring security pre-authentication example

What went wrong? If you check the console you will see the following error:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException: SM_USER header not found in request.
	at org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter.getPreAuthenticatedPrincipal

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

spring security pre-authentication

When you access URL http://localhost:8080/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.

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.

Source Code

Download

1 thought on “Spring Boot Security Pre-authentication Example

  1. Thanks for the Post. I am working on a spring boot thymeleaf template web app with pre-authentication. the app will be deployed on Tomcat. Do we need to any additional changes on the tomcat setting side for this to work when deployed?

Leave a Reply

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