Spring Security 尽管使用@WithMockUser提供角色,但控制器的Spring测试仍返回HTTP 403

gwo2fgha  于 2022-12-04  发布在  Spring
关注(0)|答案(1)|浏览(162)

由于Spring Security的原因,我正在努力设置Rest Controller测试。
我尝试测试一个基本的休息终点:

@RestController
@RequestMapping(value = "/product")
public class ProductInformationController {

  private final ProductInformationService productInformationService;

  ...

  @DeleteMapping(value = "/{id}")
  public ResponseEntity<Void> deleteProductById(@PathVariable int id) {
    productInformationService.deleteProductById(id);

    return ResponseEntity.noContent().build();
  }

基本烟雾测试如下所示:

@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WebMvcTest({ProductInformationController.class, CommonControllerAdvice.class})
class ProductInformationControllerTest {

  private static final String SINGLE_RESOURCE_ENDPOINT_URL = "/product/{id}";

  @Autowired
  MockMvc mockMvc;

  @MockBean
  ProductInformationService service;

  ...

  @Test
  @WithMockUser(roles={"USER","ADMIN"})
  void shouldReturn204ForDeleteProduct() throws Exception {
    var productId = 1;
    
    mockMvc.perform(delete(SINGLE_RESOURCE_ENDPOINT_URL, productId))
        .andExpect(status().isNoContent());
  }

我在依赖项中有Spring Security,但是将SecurityFilterChain公开为@Bean的配置不在为此WebMvcTest加载的类中。
但是,即使添加了两个理论上可能的用户角色,测试也会返回HTTP 403,甚至不会将调用传播到任何处理程序:

MockHttpServletRequest:
      HTTP Method = DELETE
      Request URI = /product/1
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@7ba1cdbe, SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN, ROLE_USER]]]}

Handler:
             Type = null
...

MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

终结点的手动测试按预期工作。任何使用GET的测试也按预期工作,其他HTTP predicate 也遇到相同的问题。
我假设问题是Spring Security的部分设置,因为我只想测试控制器,我不想包括整个Spring Security配置。
我如何才能使这个控制器测试与最少的其他类集和最精简的应用程序上下文一起工作?

oxosxuxt

oxosxuxt1#

我想我偶然发现了同样的问题。作为解决方案,我有条件地在安全链中添加了一些虚拟用户。
作为使用Spring-Boot 2.7.0版的Spring-Security进行mvc测试的基础,我创建了以下抽象基类:

import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.Filter;
import java.util.List;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public abstract class AbstractMvcTest {
    @LocalServerPort
    protected int port;

    @Autowired
    protected TestRestTemplate restTemplate;

    protected MockMvc mockMvc;

    @Autowired
    private Filter springSecurityFilterChain;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private PasswordEncoder passwordEncoder;
    private static final SimpleGrantedAuthority USER_AUTHORITY = new SimpleGrantedAuthority("USER");
    private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority("ADMIN");

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build();
    }

    protected RequestPostProcessor makeAuthorizedAdminUser() {
        return SecurityMockMvcRequestPostProcessors.user(new User(
                "Admin@example.com",
                passwordEncoder.encode("verys3cur3"),
                List.of(ADMIN_AUTHORITY, USER_AUTHORITY)));
    }

    protected RequestPostProcessor makeAuthorizedUser() {
        return SecurityMockMvcRequestPostProcessors.user(new User(
                "User@example.com",
                passwordEncoder.encode("foobar123"),
                List.of(USER_AUTHORITY)));
    }

}

具体的测试如下:

class IndexControllerTest extends AbstractMvcTest
{
    @Autowired
    private IndexController controller;

    @Test
    void contextLoads()
    {
        assertThat(controller).isNotNull();
    }

    @Test
    void userLoggedIn_returnOk() throws Exception
    {
        this.mockMvc.perform(get("/")
                        .with(makeAuthorizedUser()))
                .andDo(print())
                .andExpect(status().isOk());
    }

}

indexController只返回根目录下的index.html。

相关问题