You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security控制器测试用例返回403状态码求助

微服务添加Spring Security后控制器测试用例返回403禁止访问

问题概述

在微服务中新增Spring Security配置后,控制器的测试用例执行时返回403禁止访问状态码,无法通过预期的201创建状态校验。

控制器代码

@PostMapping("/{username}")
@PreAuthorize("hasRole('MEMBER') && #username == authentication.principal.username")
public ResponseEntity<Object> addWishlist(@Parameter(hidden = true) @RequestHeader("Authorization") String token, @PathVariable String username, @RequestBody MovieDto movie){
        return new ResponseEntity<>(wishlistService.addWishlist(username,movie),HttpStatus.CREATED);
}

测试用例代码

@Test
@WithMockUser(roles = "MEMBER", username = "user")
void testAddWishlist() throws Exception {
    String username = "user";
    MovieDto movieDto = new MovieDto();
    movieDto.setTitle("Movie 1");
    WishlistDto wishlistDto = new WishlistDto();
    wishlistDto.setUsername(username);
    wishlistDto.setMovies(List.of(movieDto));

    when(wishlistService.addWishlist(username, movieDto)).thenReturn(wishlistDto);

    mockMvc.perform(MockMvcRequestBuilders.post("/api/v1.0/private/wishlist/{username}", username)
                    .header(HttpHeaders.AUTHORIZATION, "Bearer testtoken")
                    .content("{\"id\":\"1\",\"title\":\"Movie 1\"}")
                    .contentType(MediaType.APPLICATION_JSON))
            .andExpect(MockMvcResultMatchers.status().isCreated())
            .andExpect(MockMvcResultMatchers.jsonPath("$.movies[0].title").value("Movie 1"));
}

错误日志

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /api/v1.0/private/wishlist/user
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Authorization:"Bearer testtoken", Content-Length:"28"]
             Body = {"id":"1","title":"Movie 1"}
    Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@46bfbbb9, 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_MEMBER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_MEMBER]]]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", 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 = []

java.lang.AssertionError: Status expected:<201> but was:<403>
Expected :201
Actual   :403
<Click to see difference>


    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
    at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:637)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:214)
    at com.cts.wishlistservice.controller.WishlistControllerTestMvc.testAddWishlist(WishlistControllerTestMvc.java:102)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)

修复方案

1. 修正控制器权限表达式

控制器中@PreAuthorize使用了HTML转义字符&amp;&amp;,导致SpEL无法正确解析逻辑与操作,权限校验失败。将其替换为Java原生的&&:

@PostMapping("/{username}")
@PreAuthorize("hasRole('MEMBER') && #username == authentication.principal.username")
public ResponseEntity<Object> addWishlist(@Parameter(hidden = true) @RequestHeader("Authorization") String token, @PathVariable String username, @RequestBody MovieDto movie){
        return new ResponseEntity<>(wishlistService.addWishlist(username,movie),HttpStatus.CREATED);
}

2. 优化测试用例

  • 移除冗余的Authorization请求头:@WithMockUser已经在测试上下文注入了认证信息,额外添加的token会导致上下文冲突。
  • 禁用CSRF校验:Spring Security默认对POST请求要求CSRF token,测试中禁用可简化流程。

修改后的测试用例:

@Test
@WithMockUser(roles = "MEMBER", username = "user")
void testAddWishlist() throws Exception {
    String username = "user";
    MovieDto movieDto = new MovieDto();
    movieDto.setTitle("Movie 1");
    WishlistDto wishlistDto = new WishlistDto();
    wishlistDto.setUsername(username);
    wishlistDto.setMovies(List.of(movieDto));

    when(wishlistService.addWishlist(username, movieDto)).thenReturn(wishlistDto);

    mockMvc.perform(MockMvcRequestBuilders.post("/api/v1.0/private/wishlist/{username}", username)
                    .content("{\"id\":\"1\",\"title\":\"Movie 1\"}")
                    .contentType(MediaType.APPLICATION_JSON)
                    .csrf().disable())
            .andExpect(MockMvcResultMatchers.status().isCreated())
            .andExpect(MockMvcResultMatchers.jsonPath("$.movies[0].title").value("Movie 1"));
}

内容的提问来源于stack exchange,提问作者Kollimarla Jagadeep

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:23:12