Method security testing is the process of testing the security of each method to make sure that only authorized users can access them. By testing the method-level security, we can ensure that our application is secure and that users can only perform the actions that they are authorized to do.
Let’s say we built an application that has different roles assigned to users. We want each role to be able to perform certain actions, and to do that, we need to give them the authorization to access the corresponding methods. This authorization process requires the role to be authenticated before the method can be executed. Authentication is how we confirm the identity of the user who is attempting to access a method or resource, while authorization determines what actions they are allowed to perform.
Spring Security is a framework that provides built-in authentication and authorization support. It also supports other security features for Java applications. There are two ways to carry out authentication and authorization; using HttpSecurity
and with @PreAuthorize. For this tutorial, we will focus on @PreAuthorize
, which is placed on controller methods.
In this tutorial, we will be looking at carrying out unit testing for a Spring Boot application that uses method-level security. This is done by running tests against a specific user, and for this, we need the JUnit annotations @WithMockUser
, @WithAnonymousUser
, and @WithUserDetails
. These annotations are used to mock a user on which we can run tests against.
Spring Security Test Dependency
We start by adding the spring-security-test dependency to the pom.xml file.
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
Method Under Test
The below code example contains a getAllUsers() method that requires authentication for a user to access it. In this example, authorization is needed to view all users.
public class UserImplementation implements UserService { UserRepo userRepo; @PreAuthorize("isAuthenticated()") public List<User> getAllUsers() { return userRepo.findAll(); } }
Test Methods
When we write a test that isn’t authenticated, it should return AuthenticationCredentialsNotFoundException.class
@Test public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
To test a method that requires authentication, we can use some of the following annotations in Spring Security:
@WithMockUser annotation
@WithMockUser
annotation is used to mock a user whose default name is “user” and who has a password “password“. In the Spring Security context, the user has a default role of “USER“. Also, the @WithMockUser annotation simulates that the “user” has logged in.
@Test @WithMockUser public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
@WithMockUser can also be implemented with a personalized username.
@Test @WithMockUser("lizzie") public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
In this specific case, the annotation creates a mocked user with the username “lizzie” and with default user authorities. This means that any test method annotated with @WithMockUser("lizzie")
will be executed as if the user with username “lizzie” is currently logged in and has the default authorities.
@WithMockUser can also be implemented with a personalized username, password, and role.
@Test @WithMockUser(username = "lizzie", password = "password12", role = {"USER"} public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
Just like with the @WithMockUser("lizzie")
, the above code example allows you to simulate a user who is authenticated in the system without actually going through the authentication process.
In this case, the annotation creates a mocked user with the username “lizzie”, password “password12”, and with the role “USER”. This means that any test method annotated with @WithMockUser(username = "lizzie", password = "password12", role = {"USER"})
will be executed as if the user with the specified username and password is currently logged in and has the specified role.
@WithAnonymousUser
@WithAnnonymousUser
annotation simulates an anonymous user. It is equivalent to @WithMockUser(roles = {“ANONYMOUS”}) and @WithMockUser(authorities = {“ROLE_ANONYMOUS”}). We use this annotation in scenarios where we need to test for anonymous users.
@Test @WithAnonymousUser public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
@WithUserDetails
@WithMockUser
is a helpful tool when using Spring Security for testing. It’s important to note that it may not always work, especially when applications expect a specific Authentication principal. In such cases, you can customize the “UserDetailsService” to load users from a custom service. Users loaded using this custom service are often more reliable than the ones created with @WithMockUser
.
On the other hand, @WithUserDetails
requires an existing user. This means that you need to have a user already set up in the system for this annotation to work. Unlike @WithMockUser
, it creates a real user with its associated authorities, which can be useful for testing scenarios where you need to test the behavior of your application for specific users.
Assuming the UserDetailsService is exposed as a bean, the principal returned will be the default username of user.
@Test @WithUserDetails public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
Like with @WithMockUser, we can customize the username, and the principal returned will be of the customized username.
@Test @WithUserDetails("lizzie") public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
We can also further customize our test by using the specific bean name. In this scenario, the username provided is looked up using the specified bean. In the example below, “lizzie” is looked up using the UserDetailsService with the bean name “myUserDetailsService”.
@Test @WithUserDatils(value="lizzie", userDetailsServiceBeanName = "myUserDetailsService") public void getAllUsers() throws Exception { List<User> users = userService.getAllUsers(); System.out.println(users); }
All these annotations can be placed at the class level so that all the test cases within the class use the same user.
Conclusion
In conclusion, testing method security in a Spring Boot application is crucial to ensure that access control and authorization rules are correctly implemented and enforced. By leveraging the Spring Security Test dependency and following best practices, developers can effectively test the security aspects of their application.
Looking to strengthen your expertise in testing Spring Boot applications? Explore the Testing Java Code page for a comprehensive set of tutorials that focus on testing techniques and best practices specifically tailored for Spring Boot applications. Enhance your skills and ensure the quality of your Spring Boot projects.
Frequently Asked Questions:
- What is
@WithMockUser
used for?
@WithMockUser
is used to simulate an authenticated user in Spring Security testing. It allows you to test your application’s behaviour for different types of users and roles without having to create real users or go through the full authentication process each time. - How do I use
@WithMockUser
in my tests?
You can use@WithMockUser
by adding it as an annotation to your test methods. You can specify the user’s username, password, and role if needed. - What if my application expects a specific Authentication principal?
@WithMockUser
may not always work if your application expects a specific Authentication principal. In such cases, you may need to customize the “UserDetailsService” to load users from a custom service. - How does
@WithUserDetails
differ from@WithMockUser
?
@WithUserDetails
creates a real user with its associated authorities while@WithMockUser
creates a mocked user with default user authorities.@WithUserDetails
requires an existing user, while@WithMockUser
does not. - Can I use
@WithMockUser
in integration tests? Yes, you can use@WithMockUser
in integration tests. However, you should be aware that it may not fully replicate the behaviour of a real user in the system, so you may need to perform additional testing to ensure that your application works as expected.
If you would like to learn more about the @PreAuthorized annotation, check out this tutorial Spring Security @PreAuthorize Annotation Example or Spring Method-Level Security with @PreAuthorize.