Spring-Test & Spring Security: How to mock Authentication

I was trying to figure out how to unit test if my the URLs of my controllers are properly secured. Just in case someone changes things around and accidentally removes security settings.

I set up a WebTestEnvironment like so:

import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
 
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
 
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ 
        "file:src/main/webapp/WEB-INF/spring/security.xml",
        "file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
        "file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
 
    @Resource
    private FilterChainProxy springSecurityFilterChain;
 
    @Autowired
    @Qualifier("databaseUserService")
    protected UserDetailsService userDetailsService;
 
    @Autowired
    private WebApplicationContext wac;
 
    @Autowired
    protected DataSource dataSource;
 
    protected MockMvc mockMvc;
 
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
 
        UserDetails user = this.userDetailsService.loadUserByUsername(username);
 
        UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(
                        user, 
                        user.getPassword(), 
                        user.getAuthorities());
 
        return authentication;
    }
 
    @Before
    public void setupMockMvc() throws NamingException {
 
        // setup mock MVC
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(this.wac)
                .addFilters(this.springSecurityFilterChain)
                .build();
    }
}

In my actual test I tried to do something like this:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 
import eu.ubicon.webapp.test.WebappTestEnvironment;
 
public class CopyOfClaimTest extends WebappTestEnvironment {
 
    @Test
    public void signedIn() throws Exception {
 
        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");
 
        SecurityContextHolder.getContext().setAuthentication(principal);        
 
        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
//                    .principal(principal)
                    .session(session))
            .andExpect(status().isOk());
    }
 
}

I picked this up here:
http://java.dzone.com/articles/spring-test-mvc-junit-testing
here:
http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller
here:
http://stackoverflow.com/questions/360520/unit-testing-with-spring-security
or here:
http://stackoverflow.com/questions/5403818/how-to-junit-tests-a-preauthorize-annotation-and-its-spring-el-specified-by-a-s

Yet if one looks closely this only helps when not sending actual requests to URLs, but only when testing services on a function level. In my case an "access denied" exception was thrown:

org.springframework.security.access.AccessDeniedException: Access is denied
	at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
	at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
	at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
        ...

The following two log messages are noteworthy basically saying that no user was authenticated indicating that setting the Principal did not work, or that it was overwritten.

14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS

It turned out that the SecurityContextPersistenceFilter, which is part of the Spring Security filter chain, always resets my SecurityContext, which I set calling SecurityContextHolder.getContext().setAuthentication(principal) (or by using the .principal(principal) method). This filter sets the SecurityContext in the SecurityContextHolder with a SecurityContext from a SecurityContextRepository OVERWRITING the one I set earlier. The repository is a HttpSessionSecurityContextRepository by default. The HttpSessionSecurityContextRepository inspects the given HttpRequest and tries to access the corresponding HttpSession. If it exists, it will try to read the SecurityContext from the HttpSession. If this fails, the repository generates an empty SecurityContext.

Thus, my solution is to pass a HttpSession along with the request, which holds the SecurityContext:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 
import eu.ubicon.webapp.test.WebappTestEnvironment;
 
public class CopyOfClaimTest extends WebappTestEnvironment {
 
    @Test
    public void signedIn() throws Exception {
 
        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");
 
        SecurityContextHolder.getContext().setAuthentication(principal);
 
        MockHttpSession session = new MockHttpSession();
        session.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, 
                SecurityContextHolder.getContext());
 
 
        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
                    .session(session))
            .andExpect(status().isOk());
    }
}

Alternatively a RequestPostProcessor could be used as it is used in this blog post:
https://github.com/skprasadu/spring-activiti-loanrequest
To get to the actual implementation one needs to scan through the corresponding code:
http://krishnasblog.com/2012/10/01/junit-testing-spring-security-spring-mvc-activiti-bpm/

I also posted this on StackOverflow:
http://stackoverflow.com/questions/15203485/spring-test-security-how-to-mock-authentication/15203488#15203488

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)

One Response to “Spring-Test & Spring Security: How to mock Authentication”

  1. Ronald says:

    Helped me. Thanks!

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)

Leave a Reply