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 !!
Leave a Reply