Spring Security Pre-Authentication with Spring Data JPA

Introduction

In this example I am going to show you Spring Security Pre-authentication hasrole with Spring Data JPA. In my another example I had shown the similar example on Spring Security Pre-Authentication with Spring JDBC Template.

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, these situations are referred to 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 must

  • 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.

spring security preauthentication

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 must be authorized only to perform the activities using REST URL. So here I am going to check for the SM_USER header on the HTTP requests.

Prerequisites

Java 1.8+, Spring Boot 2.5.4, Spring Data JPA 2.5.4, H2 1.4.200

Pre-Authentication – Project Setup

Create a maven based project in your favorite IDE or tool. The name of the project is spring-security-data-jpa-pre-authentication.

The following configurations in pom.xml file with required dependencies are used for this example application.

<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-data-jpa-pre-authentication</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

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

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.4</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-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
	</dependencies>

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

Spring Security Pre-Authentication – SQL Scripts

I will create here SQL scripts to create the required database tables. I will also insert a user and user’s role in the tables for testing the applications.

The following DDL statements are kept into the file schema.sql file under src/main/resources class path folder. The schema.sql name is a standard name and Spring will automatically execute the script to create the required tables.

DROP TABLE IF EXISTS `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`)
);

The following data.sql file is also kept under the same class path folder src/main/resources. This is again a standard name, so Spring will pick it up automatically.

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

If you do not use the standard naming conventions for your SQL scripts then you have to create a DataSource bean in Java configuration to pass your SQL scripts for execution:

@Bean
public DataSource dataSource() {
	EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
	return embeddedDatabaseBuilder.addScript("classpath:create-table.sql").addScript("insert-data.sql")		.setType(EmbeddedDatabaseType.H2).build();
}

application.properties

The following datasource configuration is done in the src/main/resources/application.properties file:

spring.datasource.url=jdbc:h2:mem:roytuts
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=


spring.jpa.show-sql = true

spring.h2.console.enabled = true
spring.h2.console.path=/h2console/

spring.jpa.hibernate.ddl-auto = none

spring.jpa.defer-datasource-initialization=true

I am using H2 in-memory database for this example and you can use any database.

The spring.jpa.show-sql = true will log the Hibernate SQL statements, in the console, which are executed during REST API call.

spring.h2.console.enabled = true will enable the H2 console, so you can access it in the browser to verify your data in the table after log into the H2 database.

spring.h2.console.path=/h2console/ tells that H2 console is accessible at path /h2console, so the entire URL is http://localhost:8080/h2console, where 8080 is the port of the running server at localhost.

spring.jpa.hibernate.ddl-auto = none tells not to create any table using Entity class as I am using SQL script to create tables.

spring.jpa.defer-datasource-initialization=true, is required to insert data into the tables once tables are created successfully.

Spring Security Pre-Authentication – Entity Class

The following User entity class which is responsible for retrieving/storing user details.

@Entity
public class User {

	@Id
	private String userName;
	private String userPass;
	private boolean enable;
}

The following UserRole entity class which is responsible for retrieving/storing user role details.
As you know that Hibernate requires primary key column for each table, so I have made the userName (user_name) property as primary key.

Ideally you should have proper mapping between two tables but for this example, I have just made it simple by putting @Id annotation on the same property in both tables.

@Entity
public class UserRole {

	@Id
	private String userName;
	private String userRole;
}

The getters and setters in both entity classes are there in the source code which can be downloaded later from this tutorial.

Repository Interface

The corresponding repository interfaces are given below. JpaRepository provides built-in functionalities for performing basic CRUD operations but if your requirements are not fulfilled then you can create your own query method.

public interface UserRepository extends JpaRepository<User, String> {

	Optional<User> findByUserName(String userName);

}

And

public interface UserRoleRepository extends JpaRepository<UserRole, String> {

	Optional<UserRole> findByUserName(String userName);

}

UserDetailsService

The service class will use Spring framework’s built-in UserDetailsService for retrieving the user name and user role and use it in the Spring Security.

@Service
public class CustomUserDetailsService implements UserDetailsService {

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private UserRoleRepository userRoleRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		Optional<com.roytuts.spring.security.data.jpa.preauthentication.entity.User> user = userRepository
				.findByUserName(username);

		if (!user.isPresent()) {// should have proper handling of Exception
			throw new UsernameNotFoundException("User '" + username + "' not found.");
		}

		GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
				userRoleRepository.findByUserName(username).get().getUserRole());
		UserDetails details = new org.springframework.security.core.userdetails.User(user.get().getUserName(),
				user.get().getUserPass(), Arrays.asList(grantedAuthority));

		return details;
	}

}

Spring Security Configurations

You need below security configuration using Java annotation in order to authorize the user for accessing the application. The below SecurityConfig class, which is responsible for all security configurations, extends WebSecurityConfigurerAdapter and overrides configure(HttpSecurity http) and authenticationManager() methods.
I have ignored security for static resources such as js, css, images, etc.

I have overridden method configure(HttpSecurity http) to apply 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 am just checking for the http header SM_USER exists or not for verification.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

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

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

}

Spring Security Pre-Authentication – 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 BlogRestController {

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

}

VO Class – BlogVo

This is the value object or DTO class that is used in REST controller class. You should always use VO or DTO object instead of Entity class in your REST controller or Controller class.

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

Spring Security Pre-Authentication – Main Class

In spring boot application, @SpringBootApplication annotation is enough to start and deploy a spring boot application into embedded Tomcat server.

@SpringBootApplication
public class PreAuthSecurityApp {

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

}

It is assumed that the application has authenticated first using Siteminder authentication and ideally the SM_USER header should come from Siteminder authentication in the HTTP request.

Testing the Application

Run the main class to start your spring boot application. Now when you try to access the URL http://localhost:8080/ without setting SM_USER header, you will see below error.

spring security pre-authentication hasrole

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

org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException: SM_USER header not found in request.

Setting SM_USER in the HTTP request header will give you the expected response:

spring security pre-authentication hasrole

When you access URL http://localhost:8080/blogs by setting SM_USER:roytuts.com in HTTP request header, you will get error with 403 – Forbidden. You see the following 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 hasrole

Setting HTTP request header with roy will give you the expected response:

spring security pre-authentication hasrole

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

2 thoughts on “Spring Security Pre-Authentication with Spring Data JPA

Leave a Reply

Your email address will not be published.