In the previous parts of my spring-test-mvc tutorial, we learned to write integration tests for our controllers. However, we have not talked about security yet, even though securing our data is a crucial part of (almost) every application. This is the sixth part of my spring-test-mvc tutorial and it will teach us to write integration tests for our example applications which use Spring MVC 3.1 and Spring Security 3.1.
Lets start by taking a closer look at our example applications.
The Anatomy of Our Example Applications
Both of our example applications has a REST API that provides CRUD functions for todo entries. The only difference between them is that they use different approaches for enforcing our security requirements. The first application uses url based security and the second one uses a technique called method security. The implementation of these example applications is described with more details in my blog entries called Integration Testing of Spring MVC Applications: REST API Part One and Part Two.
This section describes
- The security requirements of the implemented todo application.
- The common components that are used by both applications.
- The configuration of an example application that uses url based security.
- The configuration of an example application that uses method security.
The security configurations of both example applications were created by following the instructions given in a blog entry called Securing RESTful Web Service with Spring Security 3.1 by Eugen Paraschiv.
Lets move on and take a closer look at the security requirements of our todo application.
Security Requirements
The security requirements of our todo application are very simple. Actually, we have got only one security rule: only logged in users can perform CRUD operations for todo entries.
However, since we are securing a REST API, we have some other security related requirements which are described in the following:
- When an anonymous user tries to perform CRUD operation, our REST API must return the 401 HTTP status code.
- An user can log in by performing a POST request to url '/api/login'.
- If a login is successful, our REST API must return the 200 HTTP status code.
- If a login fails, our REST API must return the 401 HTTP status code.
- An user can log out by performing a GET request to url '/api/logout'.
- After an user has logged out successfully, our REST API must return the 200 HTTP status code.
Common Components
Both of our example applications are using some common security components that are described in the following:
- The authentication entry point is a component that starts the authentication process.
- The authentication success handler is a component that is called after a successful authentication attempt.
- The authentication failure handler is a component that is called after a failed authentication attempt.
- The logout success handler is a component that is called after a successful logout.
Lets move on and take a closer look at these components.
The Authentication Entry Point
The authentication entry point is a component that is used to start the authentication process. As we remember, one of our requirements stated that if an user tries to access a protected resource without being authenticated, our REST API must return the 401 HTTP status code. Unfortunately, Spring Security does not have a component that fulfills this requirement.
Thus, we have to implement our a custom authentication entry point by implementing the AuthenticationEntryPoint interface. Our custom authentication entry point is called the RestAuthencationEntryPoint and its source code looks as follows:
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" ); } }
The Authentication Success Handler
The authentication success handler is a component that is called when an authentication has been successful. One of our requirements stated that our REST API must return 200 HTTP status code after a successful authentication. Because Spring Security does not provide a component that we can use, we have to implement it ourselves. Our custom authentication success handler is called the RestAuthenticationSuccessHandler, and we can implement it by following these steps:
- Extend the SimpleUrlAuthenticationHandler class.
- Override the onAuthenticationSuccess() method of the AuthenticationSuccessHandler interface.
- Set the correct status code (200) to the response and clean the authentication data from session by calling the clearAuthenticationAttributes() method of the SimpleUrlAuthenticationSuccessHandler class.
The source code of the RestAuthenticationSuccessHandler class looks as follows:
import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { response.setStatus(HttpServletResponse.SC_OK); clearAuthenticationAttributes(request); } }
The Authentication Failure handler
The authentication failure handler is a component that is called after a failed authentication attempt. One of our requirements stated that if an authentication attempt fails, our REST API must return the 401 HTTP status code. We can fulfill this requirement by creating a custom authentication failure handler that implements the AuthenticationFailureHandler interface. The source code of the RestAuthenticationFailureHandler class looks as follows:
import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Bad credentials"); } }
The Logout Success Handler
The logout success handler is a component that called after a successful logout. One of our requirement states that our REST API should notify the user about this by returning the 200 HTTP status code. In order to fulfill this requirement, we have to implement a custom logout success handler that implements the LogoutSuccessHandler interface. The source code of the RestLogoutSuccesHandler looks as follows:
import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class RestLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_OK); } }
Url Based Security
The first example application uses url based security to ensure that only authorized user can access protected resources. Its security configuration is found from a file called exampleApplicationContext-security.xml. We can configure our todo application to use url based security by following these steps:
- Ensure that everyone can access static resources such as css files, images and Javascript files. We can do this using the http element of the security namespace. All we have to do is to set the wanted url pattern (in our case /static/**) and set the value of its security attribute to 'none'.
- Configure our custom authentication entry point, authentication success handler, authentication failure handler and logout success handler beans.
- Configure the used authentication manager and set the used implementation of the UserDetailsService interface. In this example we will use an implementation that stores the username and password in memory.
- Create a custom configuration for the login filter bean. This configuration is required because we want to use custom authentication success and failure handlers instead of the default handlers. Set a reference to the used authentication manager bean and the url that is processed by the login filter. Set non mandatory properties such as usernameParameter, passwordParameter and the postOnly.
- Enable spring security by using the security namespace’s http element and set a reference to the used authentication entry point bean.
- Configure the security rules of our REST API by using the intercept-url element of the security namespace.
- Add a custom login filter to the Spring Security filter chain and ensure that this filter replaces the default login filter.
- Add a logout filter to the Spring security namespace and set the url processed by this filter.
The content of our security configuration file looks as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!-- Disable security on static resources --> <security:http pattern="/static/**" security="none"/> <!-- Enable Spring Security --> <security:http entry-point-ref="restAuthenticationEntryPoint" use-expressions="true"> <!-- Configures the security rules of our REST API --> <security:intercept-url pattern="/api/user" access="permitAll()"/> <security:intercept-url pattern="/api/**" access="hasRole('ROLE_USER')"/> <!-- Adds the custom login filter to Spring Security filter chain --> <security:custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/> <!-- Adds a logout filter to Spring Security filter chain --> <security:logout logout-url="/api/logout" delete-cookies="true" invalidate-session="true" success-handler-ref="restLogoutSuccessHandler"/> </security:http> <!-- Configures the authentication entry point that returns HTTP status code 401 --> <bean id="restAuthenticationEntryPoint" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationEntryPoint"/> <!-- Configures a custom login filter bean --> <bean id="loginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationFailureHandler" ref="restAuthenticationFailureHandler"/> <property name="authenticationSuccessHandler" ref="restAuthenticationSuccessHandler"/> <property name="filterProcessesUrl" value="/api/login"/> <property name="usernameParameter" value="username"/> <property name="passwordParameter" value="password"/> <property name="postOnly" value="true"/> </bean> <!-- Configures a custom authentication success handler that returns HTTP status code 200 --> <bean id="restAuthenticationSuccessHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationSuccessHandler"/> <!-- Configures a custom authentication failure handler that returns HTTP status code 401 --> <bean id="restAuthenticationFailureHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationFailureHandler"/> <!-- Configures a custom logout success handler that returns HTTP status code 200 --> <bean id="restLogoutSuccessHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestLogoutSuccessHandler"/> <!-- Configures in-memory implementation of the UserDetailsService implementation --> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider> <security:user-service> <security:user name="user" password="password" authorities="ROLE_USER"/> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
Method Security
The second example application uses a technique called method security that is used to protect unauthorized access to secured methods. Also, instead of using roles in the security expressions, this application uses permissions to decide whether the user has the right to perform an operation to a todo entry.
This subsection describes how we can
- Create a custom permission evaluator.
- Create the security configuration of our application.
- Add the security constraints to the secured methods.
Custom Permission Evaluator
Because our todo application uses a permission system to decide if the user can perform a specific operation to the domain object, we have to create a component that is used to evaluate if the user can perform the requested operation. This component is called called a permission evaluator. We can create a custom permission evaluator by implementing the PermissionEvaluator interface that declares two methods:
- The boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) method is used decide if the user can perform the requested operation for a target domain object. This method is called when the target domain object is already available.
- The boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) method is used in a situation where only the id of the domain object is available instead of the actual domain object.
The name of our custom permission evaluator is TodoPermissionEvaluator. We will implement only the first method of the PermissionEvaluator interface because in this application we don’t have need for the second one. An user is allowed to perform CRUD operations for todo entries if the following conditions are met:
- The domain object is a todo entry. In this example we use strings in our security expressions to identify the target domain object. This approach does not work if we have to evaluate the information of the domain object. However, for the sake of simplicity we will use this approach in this example application.
- The principal object is an instance of the UserDetails interface. In other words, the user is not an anonymous user.
- The logged in user has a role called 'ROLE_USER'.
The source code of the TodoPermissionEvaluator class looks as follows:
import net.petrikainulainen.spring.testmvc.user.dto.SecurityRole; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; public class TodoPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { boolean hasPermission = false; if (targetDomainObject.equals("Todo")) { Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails = (UserDetails) principal; String principalRole = getRole(userDetails.getAuthorities()); if (principalRole.equals(SecurityRole.ROLE_USER.name())) { hasPermission = true; } } } return hasPermission; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { //Not required here. return false; } private String getRole(Collection<? extends GrantedAuthority> authorities) { return authorities.iterator().next().getAuthority(); } }
Configuration
The name of our security configuration file exampleApplicationContext-security.xml and we can create the security configuration of our todo application by following these steps:
- Ensure that everyone can access static resources such as css files, images and Javascript files. We can do this using the http element of the security namespace. All we have to do is to set the wanted url pattern (in our case /static/**) and the value of its security attribute to 'none'.
- Configure our custom authentication entry point, authentication success handler, authentication failure handler and logout success handler beans.
- Configure the used authentication manager and set the used implementation of the UserDetailsService interface. In this example we will use an implementation that stores the username and password in memory.
- Create a custom configuration for the login filter bean. This configuration is required because we want to use custom authentication success and failure handlers instead of the default handlers. We also must set a reference to the used authentication manager bean and the url that is processed by the login filter. We also set some non mandatory properties such as usernameParameter, passwordParameter and the postOnly parameter.
- Enable spring security by using the security namespace’s http element and set a reference to the used authentication entry point bean.
- Add a custom login filter to the Spring security namespace and ensure that this filter replaces the default login filter.
- Add a logout filter to the Spring security namespace and set the url processed by this filter.
- configure our permission evaluator bean.
- Configure an expression handler bean and set a reference to our custom permission evaluator bean.
- Enable the @Pre and @Post security annotations and set a reference to the used expression handler bean. We can do this by using the global-method-security and the expression-handler elements of the security namespace.
The content of our security configuration file looks as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!-- Disable security on static resources --> <security:http pattern="/static/**" security="none"/> <!-- Enable Spring Security --> <security:http entry-point-ref="restAuthenticationEntryPoint" use-expressions="true"> <!-- Adds the custom login filter to the Spring Security filter chain --> <security:custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/> <!-- Add the logout filter to the Spring Security filter chain --> <security:logout logout-url="/api/logout" delete-cookies="true" invalidate-session="true" success-handler-ref="restLogoutSuccessHandler"/> </security:http> <!-- Enable @PreAuthorize and @PostAuthorize annotations --> <security:global-method-security pre-post-annotations="enabled"> <!-- Adds a reference to the used expression handler bean --> <security:expression-handler ref="expressionHandler"/> </security:global-method-security> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator" ref="todoPermissionEvaluator" /> </bean> <bean id="todoPermissionEvaluator" class="net.petrikainulainen.spring.testmvc.security.authorization.TodoPermissionEvaluator"/> <!-- Configures the authentication entry point --> <bean id="restAuthenticationEntryPoint" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationEntryPoint"/> <!-- Configures a custom login filter bean --> <bean id="loginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationFailureHandler" ref="restAuthenticationFailureHandler"/> <property name="authenticationSuccessHandler" ref="restAuthenticationSuccessHandler"/> <property name="filterProcessesUrl" value="/api/login"/> <property name="usernameParameter" value="username"/> <property name="passwordParameter" value="password"/> <property name="postOnly" value="true"/> </bean> <!-- Configures a custom authentication success handler that returns the HTTP status code 200 instead of the 301 HTTP status code --> <bean id="restAuthenticationSuccessHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationSuccessHandler"/> <!-- Configures a custom authentication failure handler --> <bean id="restAuthenticationFailureHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestAuthenticationFailureHandler"/> <!-- Configures a custom logout success handler --> <bean id="restLogoutSuccessHandler" class="net.petrikainulainen.spring.testmvc.security.authentication.RestLogoutSuccessHandler"/> <!-- Configures in-memory implementation of the UserDetailsService implementation --> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider> <security:user-service> <security:user name="user" password="password" authorities="ROLE_USER"/> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
Declaring Security Restrictions
The security restrictions of our todo application are declared on the service layer. The security layer of our todo application consists of one interface called TodoService and one class that implements this interface. We can now declare security restrictions in the RepositoryTodoService class by following these steps:
- Annotate the secured method with the @PreAuthorize annotation.
- Use the hasPermission() expression to declare the used security restriction.
The following example demonstrates a security restriction which states that an user can add new todo entries only if he has 'add' permission to 'Todo' domain object:
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class RepositoryTodoService implements TodoService { @PreAuthorize("hasPermission('Todo', 'add')") @Transactional @Override public Todo add(TodoDTO added) { //The implementation of this method goes here } }
Writing Integration Tests
We are now familiar with the security configuration of our example application and we can finally start writing integration tests which ensure that our security configuration is working correctly. This section describes the common testing utilities which are used in our integration tests, the configuration of our integration tests, and the integration testing of authentication and authorization.
Common Testing Utilities
Our integration tests are using four testing utilities that are described in the following:
- The TodoTestUtil class is used in the unit and integration tests or our todo application.
- The IntegrationTestUtil class is used only in our integration tests.
- The DBUnit dataset file is used to inialize our database to a known state before our integration tests are run.
- The spring-test-mvc sample utility classes are used to add support for instantiating a WebApplicationContext object and to provide support for creating a security context for Spring Security.
These utilities are described with more details in the following.
The TodoTestUtil Class
The TodoTestUtil class has one static method that is used in our integration tests. The TodoDTO createDTO(Long id, String description, String title) method is used to create new TodoDTO objects. The source code of the TodoTestUtil class looks as follows:
import net.petrikainulainen.spring.testmvc.todo.dto.TodoDTO; public class TodoTestUtil { public static TodoDTO createDTO(Long id, String description, String title) { TodoDTO dto = new TodoDTO(); dto.setId(id); dto.setDescription(description); dto.setTitle(title); return dto; } }
The IntegrationTestUtil Class
The IntegrationTestUtil class has two purposes:
First, it declares a constact called APPLICATION_JSON_UTF8 that is used to set the content type and character set of HTTP requests and to verify the content type and character set of HTTP responses.
Second, it contains a static byte[] convertObjectsToJsonBytes(Object object) method that is used to convert object to bytes that contains the JSON representation of the object. The implementation of this method consists of following steps:
- Create a new ObjectMapper object.
- Configure the created object to include only non null properties of the serialized object.
- Convert the object as json string and return the created string as byte array.
The source code of the IntegrationTestUtil class looks as follows:
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.springframework.http.MediaType; import java.io.IOException; import java.nio.charset.Charset; public class IntegrationTestUtil { public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); public static byte[] convertObjectToJsonBytes(Object object) throws IOException { ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL); return mapper.writeValueAsBytes(object); } }
The DBUnit Dataset
Each integration test use the same DBUnit dataset file to initialize the database to a known state before tests are run. The name of our dataset file is toDoData.xml and its content looks as follows:
<dataset> <todos id="1" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Foo" version="0"/> <todos id="2" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Bar" version="0"/> </dataset>
The spring-test-mvc Sample Utility Classes
The spring-test-mvc samples contain three utility classes that we will use in our integration tests. These classes are described with more details in the following:
- The GenericWebContextLoader is a class that provides a support for creating WebApplicationContext objects.
- The WebContextLoader class is an application specific extension to the GenericWebContextLoader class, and it provides us access to the WebApplicationContext of our application.
- The SecurityRequestPostProcessor class is used to create security context for Spring Security.
Configuration
The Configuration of our integration tests is a bit different than the approach described in the first part of my spring-test-mvc tutorial. We can configure our integration tests by following these steps:
- Use the @RunWith annotation to configure the used test runner. In this case we must use the SpringJUnit4ClassRunner class to run our integration tests.
- Use the @ContextConfiguration annotation to configure either the application context configuration class or the xml configuration file. Set the value of its loader property to WebContextLoader.class.
- Use the @TestExecutionListener annotation to add test execution listeners required by the Spring-Test-DBUnit. If you are not familiar with the Spring-Test-DBUnit, you might want to check out the second part of my spring-test-mvc-tutorial. Naturally, we can skip this phase if our tests are not using database.
- Use the @DatabaseSetup annotation to specify the DBUnit dataset file that is used to initialize our database to a known state before tests are run. We can skip this test if our tests are not using database.
- Add FilterChainProxy field to our test class and annotate it with the @Resource annotation. This field contains a reference to the Spring security filter chain.
- Add WebApplicationContext field to our test class and annotate it with the @Resource annotation. This field contains a reference to the used web application context.
- Add a MockMvc field to our test class. This field contains a reference to the MockMvc object that is used in our integration tests.
- Create a public setUp() method and annotate this method with the @Before annotation. This method creates a new MockMvc object by using the static webApplicationContextSetup() method of the MockMvcBuilders class and adds the Spring Security filter chain into our test.
The source code of our integration test skeleton class looks as follows:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import net.petrikainulainen.spring.testmvc.config.ExampleApplicationContext; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class }) @DatabaseSetup("toDoData.xml") public class ITTest { @Resource private FilterChainProxy springSecurityFilterChain; @Resource private WebApplicationContext webApplicationContext; private MockMvc mockMvc; @Before public void setUp() { mockMvc = MockMvcBuilders.webApplicationContextSetup(webApplicationContext) .addFilter(springSecurityFilterChain) .build(); } //Add test methods here }
Authentication Tests
First, we will write integration tests which ensure that the login and logout functions of our REST API are working correctly. We have to write four integration tests for these filters:
- We must ensure that if the user logins by using incorrect credentials, the 401 HTTP status code is returned.
- We must ensure that if the user tries to login by using incorrect request method (POST is the only accepted method), the 401 HTTP status code is returned.
- We must verify that if the user logins by using correct credentials, the 200 HTTP status code is returned.
- We must ensure that after the user has logged out, the 200 HTTP status code is returned.
These tests are described with more details in the following.
Login by Using Incorrect Credentials
We can write the first integration test by following these steps:
- Perform a POST request to url '/api/login'. Set the content type of the request to 'application/x-www-form-urlencoded'. Add the incorrect username and password as request parameters.
- Verify that the returned HTTP status code is 401.
The source code of our integration test looks as follows:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.http.MediaType; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) public class ITAuthenticationTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test public void loginWithIncorrectCredentials() throws Exception { mockMvc.perform(post("/api/login") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("username", "user1") .param("password", "password1") ) .andExpect(status().isUnauthorized()); } }
Login by Using Incorrect Request Method
We can write the second integration test by following these steps:
- Perform a GET request to url '/api/login'. Add the correct username and password as request parameters.
- Verify that the 401 HTTP status code is returned.
The source code of our integration test looks as follows:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) public class ITAuthenticationTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test public void loginByUsingIncorrectRequestMethod() throws Exception { mockMvc.perform(get("/api/login") .param("username", "user") .param("password", "password") ) .andExpect(status().isUnauthorized()); } }
Login by Using Correct Credentials
We can write the third integration test by following these steps:
- Perform a POST request to url ‘/api/login’. Set the content type of the request to ‘application/x-www-form-urlencoded’. Add the correct username and password as request parameters.
- Verify that the returned HTTP status code is 200.
The source code of our integration test looks as follows:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.http.MediaType; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) public class ITAuthenticationTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test public void loginWithCorrectCredentials() throws Exception { mockMvc.perform(post("/api/login") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("username", "user") .param("password", "password") ) .andExpect(status().isOk()); } }
Logout
We can write the fourth integration test by following these steps:
- Perform a GET request to url '/api/logout'. Use the static userDetailsService() method of the SecurityRequestPostProcessor class to set the logged in user. The username given as a method paremeter specifies the user which is used when the request is performed.
- Verify that the 200 HTTP status code is returned.
The source code of our integration test looks as follows:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; import static org.springframework.test.web.server.samples.context.SecurityRequestPostProcessors.userDetailsService; /** * @author Petri Kainulainen */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) public class ITAuthenticationTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test public void logout() throws Exception { mockMvc.perform(get("/api/logout") .with(userDetailsService("user")) ) .andExpect(status().isOk()); } }
Authorization Tests
In order to ensure that our security restrictions are working as expected, we should write two integration tests for each method of our REST API. These tests are described in the following:
- We should write a test that ensures that the 401 HTTP status code is returned when an anonymous user tries to access our REST API.
- We should write a test that ensures that a logged in user can access our REST API.
As an example, we will write these integration tests for the API method that is used to add new todo entries to the database.
Access Protected Resource as Anonymous User
We can write the first integration test by following these steps:
- Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
- Create a new TodoDTO object, and set its title and description.
- Perform a POST request to url '/api/todo'. Set the content type of the request to 'application/json'. Set the character set of the request to 'UTF8'. Transform the created TodoDTO object into correct format and send it in the body of the request.
- Verify that the 401 HTTP status code is returned.
The source code of our integration test looks as follows:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.ExpectedDatabase; import net.petrikainulainen.spring.testmvc.IntegrationTestUtil; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class }) @DatabaseSetup("toDoData.xml") public class ITTodoControllerTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test @ExpectedDatabase("toDoData.xml") public void addAsAnonymous() throws Exception { TodoDTO added = TodoTestUtil.createDTO(null, "description", "title"); mockMvc.perform(post("/api/todo") .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8) .body(IntegrationTestUtil.convertObjectToJsonBytes(added)) ) .andExpect(status().isUnauthorized()); } }
Access Protected Resources as Logged In User
We can write the second integration test by following these steps:
- Use the @ExpectedDatabase annotation to ensure that a new todo entry is added to the used database.
- Create a new TodoDTO object, and set its title and description.
- Perform a POST request to url '/api/todo'. Set the content type of the request to 'application/json'. Set the character set of the request to ‘UTF8’. Transform the created TodoDTO object into correct format and send it in the body of the request.
- Use the static userDetailsService() method of the SecurityRequestPostProcessor class to set the logged in user. The username given as a method parameter specifies the user that is used when the request is performed.
- Verify that the 200 HTTP status code is returned.
- Verify that the content type of the response is 'application/json' and its character set is 'UTF8'.
- Verify that the information of the added todo entry is returned correctly.
The source code of our integration test looks as follows:
import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.ExpectedDatabase; import com.github.springtestdbunit.assertion.DatabaseAssertionMode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.test.web.server.MockMvc; import org.springframework.test.web.server.samples.context.WebContextLoader; import org.springframework.test.web.server.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.annotation.Resource; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.server.result.MockMvcResultMatchers.content; import static org.springframework.test.web.server.result.MockMvcResultMatchers.status; import static org.springframework.test.web.server.samples.context.SecurityRequestPostProcessors.userDetailsService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class}) //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"}) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class }) @DatabaseSetup("toDoData.xml") public class ITTodoControllerTest { //Add FilterChainProxy and WebApplicationContext here private MockMvc mockMvc; //Add the setUp() method here @Test @ExpectedDatabase(value="toDoData-add-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT) public void addAsUser() throws Exception { TodoDTO added = TodoTestUtil.createDTO(null, "description", "title"); mockMvc.perform(post("/api/todo") .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8) .body(IntegrationTestUtil.convertObjectToJsonBytes(added)) .with(userDetailsService("user")) ) .andExpect(status().isOk()) .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8)) .andExpect(content().string("{\"id\":3,\"description\":\"description\",\"title\":\"title\"}")); } }
The name of the DBUnit dataset file which is used verify that a new todo entry is added to database is toDoData-add-expected.xml. Its content looks as follows:
<dataset> <todos id="1" description="Lorem ipsum" title="Foo" version="0"/> <todos id="2" description="Lorem ipsum" title="Bar" version="0"/> <todos id="3" description="description" title="title" version="0"/> </dataset>
Summary
We have now learned how we can write integration tests which ensure that our application is secured correctly. This tutorial taught us two things:
- We learned to configure our security tests.
- We learned to specify the logged in user in our integration tests.
When Spring Framework 3.2 was released, spring-test-mvc was officially included in this release under the name Spring MVC Test Framework. The next part of this tutorial describes how we can we configure our integration tests when we are using the Spring Framework 3.2.
P.S. You can get the first and the second example application from Github.
where i can find org.springframework.test.web.server.samples.context.WebContextLoader.
i have used spring-mvc-test 1.0.0.M2 but that class didn't exist
i found it here https://github.com/SpringSource/spring-test-mvc/tree/master/src/test/java/org/springframework/test/web/server/samples/context
but how to import it
thank you
Are you using Spring 3.1?
If the answer to this question is yes, you have to copy those classes to your own code base. Do you think that this should be mentioned in this blog post?
If you are using Spring 3.2, You don't need those classes. Instead you must annotate your test classes with the
@WebAppConfiguration
annotation.I hope that this answered to your question.
clearAuthenticationAttributes(request) seems to be undefined for me. Its there in the parent classes. Was it made protected recently and hence I cannot access it ?
Anyways, amazing article. Really impressed.
Regards,
Franklin
Thank you for your kind words. I really appreciate them!
Which version of Spring Security are you using?
I checked that the
clearAuthenticationAttributes(HttpServletRequest request)
method of theSimpleUrlAuthenticationSuccessHandler
class is protected in both Spring Security 3.1 and 3.2 (not stable yet).Thus, you should be able to use it as long as your success handler class extends the
SimpleUrlAuthenticationSuccessHandler
class.If you can give me more details about your problem, I am more than happy to help you out.
thanks. great article :)
You are welcome!
Hi,
Excellent article!!.
But I have question on your note on method security.
Note: This is not the recommended way to implement permission based security restrictions but it is suitable for our needs.
Any reason why do you add that note ?. Is there any better approach ?
Thanks,
Hi,
I am not sure about this but I think that the reason why I wrote that comment was that evaluating all entity related permissions in a single class can become a mess if you have to implement context specific authorization rules.
If I remember correctly, at the time I thought that using custom SPEL expressions is a better approach because it gives me the possibility to describe security rules by using a "domain specific language". However, at the moment I am not sure if this is a good decision.
Anyway, I will remove that remark because it is a bit confusing. Thanks for pointing this out!
Hi Petri,
Nice article. Just want to see the code for ExampleApplicationContext class.
Thanks.
Hi Raj,
you can get both example applications from Github (annotation based security and url based security). Both of them use the same
ExampleApplicationContext
class.I know this a fairly old article but you might be able to provide some input with a problem I'm trying to get my head around.
I'm trying to test a Controller which makes use of Spring Security, in production the application context provides a JdbcUserDetailsManager which will look up users from a database. I'm trying to re-use the same context for tests but I would like to use an InMemoryUserDetailsManager instead, what would be the best way of doing this? I could easily duplicate the production context for tests but I doesn't seem like a good idea as it would quickly become out of sync. Most examples I've seen just show a test context and don't really mention the production side of things.
Thanks
Hi Rob,
One way to do this is to use bean definition profiles. I used this technique in the example application of my Spring Data JPA tutorial because I wanted to use different date and time provider when I am running integration tests.
I did this by following these steps:
First, I created a simple class that contains the available profiles (I wanted to avoid typos in the profile names):
Second, I declared the beans and annotated the
@Bean
methods with the@Profile
annotation:Third, I configured the active profile. I simply annotated my integration tests with the
@ActiveProfiles
annotation and set its value toProfiles.INTEGRATION_TEST
. Also, since you need to ensure that your application uses the correct profile, you need to modify yourWebApplicationInitializer
. This blog post describes how you can do it.If you have any further questions, don't hesitate to ask them.
Thank you very much for your good post
I was impressed by your spring social source and all of these
I subscribed your site!!!!
I think your code is one of the best practice
1 suggestion is why not you put the banners at least people can click around as an appreciation
I hope your site succeed
thx
Thank you for kind words. I really appreciate them.
By the way, I haven't added banners on my website since I want to advertise my own product (Test With Spring Course) instead.
By the way, I could not find a price of this course
Any rough price range for indivisual ?
if it is too early to discuss please email me just in case if it helps
I want to see whether I can get an approval to buy this lesson in the company that I am working
thank you
Hi,
Check out the end of my landing page. The actual price depends from your location because it determines whether or not you have to pay VAT (that page adds VAT to the price of the course automatically if required).
It seems that I have to redesign my landing page. It is not a good that people cannot cannot find the prices of the course. Maybe it's a bit too long.
thank you :)
You are welcome!