Learn to test Spring security authentication using JUnit testcase using InMemoryDaoImpl
. Also learn to build fully populated authentication
object programmatically and then use it in application.
SecurityContextHolder
Spring security is based on security context, which is kind of static in nature. This essentially means that your do not need to inject its reference into your beans or classes in spring container. You can access the spring context anytime simply using SecurityContextHolder.getContext() method.
This context has the reference of actual principal or user which we have to validate for its access permissions.
Unit test Spring Security
I am creating a very simple maven project and will write minimal code so that I can focus on testing only what is in scope of this post i.e. authentication. Then I will write a demo service class with a single method which required “ROLE_USER
” to access it. If you try to access this method and you do not have “ROLE_USER
“, you will get the expected AccessDeniedException
. Simple enough, isn’t it?
Step 1) Project setup
Lets create the java project using below command:
$ mvn archetype:generate -DgroupId=com.howtodoinjava -DartifactId=SpringPasswordHashingDemo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Now update the pom.xml
with below dependencies and run command mvn:eclipse:eclipse
to make project eclipse supported.
<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.howtodoinjava</groupId> <artifactId>SpringPasswordHashingDemo</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>SpringPasswordHashingDemo</name> <url>http://maven.apache.org</url> <properties> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> </properties> <dependencies> <!-- Spring Core --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${org.springframework.version}</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${org.springframework.version}</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${org.springframework.version}</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${org.springframework.version}</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies> </project>
Step 2) Create security configuration file
I have create application-security.xml
file and put the security configuration inside it.
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" 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.0.3.xsd"> <global-method-security secured-annotations="enabled" /> <authentication-manager alias="authenticationManager"> <authentication-provider> <user-service> <user name="lokesh" password="password1" authorities="ROLE_USER" /> <user name="admin" password="password2" authorities="ROLE_ADMIN" /> </user-service> </authentication-provider> </authentication-manager> <beans:bean id="demoService" class="com.howtodoinjava.DemoService"/> </beans:beans>
Step 3) Create secured method
package com.howtodoinjava; import org.springframework.security.access.annotation.Secured; public class DemoService { @Secured("ROLE_USER") public void method() { System.out.println("Method called"); } }
Step 4) Test the authentication with JUnit test
In junit tests, we will configure the spring context programmatically and then will access the users by username from default user details service. In out case, it is in-memory implementation which in your case might differ to some jdbc based user details service or some other custom user detail service also. So, please modify the look up accordingly.
We will test various scenarios like valid user, invalid user, invalid role etc. You can add/ remove scenarios based on your choice.
package com.howtodoinjava; import java.util.ArrayList; import java.util.List; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.memory.InMemoryDaoImpl; public class TestDemoService { static ApplicationContext applicationContext = null; static InMemoryDaoImpl userDetailsService = null; /** * Initialize the application context to re-use in all test cases * */ @BeforeClass public static void setup() { //Create application context instance applicationContext = new ClassPathXmlApplicationContext("application-security.xml"); //Get user details service configured in configuration userDetailsService = applicationContext.getBean(InMemoryDaoImpl.class); } /** * Test the valid user with valid role * */ @Test public void testValidRole() { //Get the user by username from configured user details service UserDetails userDetails = userDetailsService.loadUserByUsername ("lokesh"); Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); DemoService service = (DemoService) applicationContext.getBean("demoService"); service.method(); } /** * Test the valid user with INVALID role * */ @Test (expected = AccessDeniedException.class) public void testInvalidRole() { UserDetails userDetails = userDetailsService.loadUserByUsername ("lokesh"); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new GrantedAuthorityImpl("ROLE_INVALID")); Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), authorities); SecurityContextHolder.getContext().setAuthentication(authToken); DemoService service = (DemoService) applicationContext.getBean("demoService"); service.method(); } /** * Test the INVALID user * */ @Test (expected = AccessDeniedException.class) public void testInvalidUser() { UserDetails userDetails = userDetailsService.loadUserByUsername ("admin"); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new GrantedAuthorityImpl("ROLE_INVALID")); Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), authorities); SecurityContextHolder.getContext().setAuthentication(authToken); DemoService service = (DemoService) applicationContext.getBean("demoService"); service.method(); } }
As you can see that all the test cases are passing as expected.
Happy Leaning !!
riyaz
Excellent sir …. very helpful blog and got a lot and lot of useful information.. Thanks alot
Johne
can you show how to achieve this using annotation based Security Config?
Leena
Hi Lokesh – Do you know how we can set the spring user context or principal?
Lokesh Gupta
Is there anything else, you are looking for and that is not given in article?
Leena
Yes, its not related to this article, basically I am looking for the way to set spring user context or principal into the SecurityContext without the use of AuthenticationManager set in XML file. Could you please help ?
Authentication authToken = new UsernamePasswordAuthenticationToken(username, password,authorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
Not sure how we can checked if its correctly set
Ibai
Great article about spring SecurityContext. Thanks.
Guillaume
Nice article, exactly what i was looking for !
Ramakrishna Punjal
A very good article … Thank you very much