Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Spring Boot Security simply

I'm struggling with testing access control on URLs protected by Spring Security.

The configuration looks like this:

    http
            .authorizeRequests()
            .antMatchers("/api/user/**", "/user").authenticated()
            .antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasAuthority("ADMIN")
            .anyRequest().permitAll();

And the test class looks like this:

package com.kubukoz.myapp;

import com.kubukoz.myapp.config.WebSecurityConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
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;

import javax.transaction.Transactional;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {MyApplication.class, WebSecurityConfig.class})
@WebAppConfiguration
@TransactionConfiguration(defaultRollback = true)
@Transactional(rollbackOn = Exception.class)
public class MyApplicationTests {

    @Autowired
    private WebApplicationContext context;
    private MockMvc mockMvc;
    @Autowired
    private FilterChainProxy filterChainProxy;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .dispatchOptions(true)
                .addFilters(filterChainProxy)
                .build();
    }

    @Test
    public void testAnonymous() throws Exception {
        mockMvc.perform(get("/api/user/account")).andExpect(status().is3xxRedirection());
    }

    @Test
    public void testUserAccessForAccount() throws Exception{
        mockMvc.perform(get("/api/user/account")).andExpect(status().isOk());
    }
}

What's the easiest way to make the last two tests pass? @WithMockUser didn't work.

like image 600
Jakub Kozłowski Avatar asked Aug 04 '15 14:08

Jakub Kozłowski


People also ask

What is @WithMockUser?

@WithMockUser provides a mock user, password and role to test any spring security method annotated with @PreAuthorize and @PostAuthorize etc. The mock user is not necessary to be present. The default user is user, password is password and role is USER.


2 Answers

You should not add the FilterChainProxy directly. Instead, you should apply SecurityMockMvcConfigurers.springSecurity() as indicated by the reference. An example is included below:

mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())
            .build();

The result of this is:

  • the FilterChainProxy is added as a Filter to MockMvc (as you did)
  • the TestSecurityContextHolderPostProcessor is added

Why is TestSecurityContextHolderPostProcessor necessary? The reason is that we need to communicate the current user from the test method to the MockHttpServletRequest that is created. This is necessary because Spring Security's SecurityContextRepositoryFilter will override any value on SecurityContextHolder to be the value found by the current SecurityContextRepository (i.e. the SecurityContext in HttpSession).

Update

Remember anything that contains role in the method name automatically prefixes "ROLE_" to the string that was passed in.

Based on your comment, the problem is you need to either update your configuration to use hasRole instead of hasAuthority (since your annotation is using roles):

.authorizeRequests()
            .antMatchers("/api/user/**", "/user").authenticated()
            .antMatchers("/api/admin/**", "/templates/admin/**", "/admin/**").hasRole("ADMIN")
            .anyRequest().permitAll();

Alternatively

You in Spring Security 4.0.2+ you can use:

@WithMockUser(authorities="ADMIN")
like image 176
Rob Winch Avatar answered Sep 27 '22 16:09

Rob Winch


Okay, figured it out.

mockMvc.perform(get("/api/user/account")
      .with(user("user")))
      .andExpect(status().isOk());

It works now.

like image 30
Jakub Kozłowski Avatar answered Sep 27 '22 15:09

Jakub Kozłowski