You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何Mock SpringSecurity与OncePerRequestFilter(JWT认证过滤器)获取@AuthenticationPrincipal?

我之前在做Spring Boot 2.0项目的时候,也碰到过类似的Mock Spring Security和JWT过滤器的问题,折腾了好一阵子才整理出几套可行的方案,结合你的场景给你详细拆解下:

整体思路

因为你用了@SpringBootTest启动完整的Web上下文,所以核心思路要么是Mock掉JWT过滤器的校验逻辑,要么是直接模拟Security上下文让@AuthenticationPrincipal能拿到用户,或者两种方式结合,根据你的测试需求来选。

方案1:Mock JWT过滤器,手动注入认证用户

这个方案适合不需要测试过滤器本身逻辑,只需要验证控制器业务的场景:

  1. 首先在测试类里用@MockBean替换掉你的JwtFilter,这样它不会执行实际的令牌校验;
  2. 在测试前置方法里手动构造认证对象,塞到Security上下文中,让@AuthenticationPrincipal能获取到。

代码示例:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class)
@AutoConfigureMockMvc
public class YourControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // 用Mock替换掉实际的JWT过滤器,跳过令牌校验
    @MockBean
    private JwtFilter jwtFilter;

    @BeforeEach
    void setUp() {
        // 构造模拟的用户对象(替换成你项目里的UserDetails实现类)
        UserDetails mockUser = User.withUsername("testUser")
                .password("dummyPassword")
                .roles("USER")
                .build();

        // 创建认证对象,把模拟用户作为principal
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                mockUser, null, mockUser.getAuthorities());

        // 设置到Security上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    @AfterEach
    void tearDown() {
        // 清理上下文,避免测试间互相影响
        SecurityContextHolder.clearContext();
    }

    // 测试示例:验证接口能正确获取到用户
    @Test
    void testApiWithAuthenticationPrincipal() throws Exception {
        mockMvc.perform(get("/your/api/endpoint"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("testUser")));
    }
}
方案2:用Spring Security测试注解模拟自定义用户

如果你的@AuthenticationPrincipal绑定的是自定义用户对象(不是默认的UserDetails),这个方案更贴合Spring Security的测试生态:

  1. 先定义一个自定义的测试注解,用来标记需要模拟的用户;
  2. 实现对应的Security上下文工厂,构造你的自定义用户对象;
  3. 在测试方法上直接用这个注解,自动注入模拟用户。

代码示例:

首先创建自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithCustomUserSecurityContextFactory.class)
public @interface WithCustomUser {
    String username() default "testUser";
    String[] roles() default {"USER"};
}

然后实现上下文工厂:

public class WithCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomUser> {
    @Override
    public SecurityContext createSecurityContext(WithCustomUser annotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        // 构造你的自定义用户对象(替换成项目里的实际类)
        CustomUser customUser = new CustomUser();
        customUser.setUsername(annotation.username());
        customUser.setRoleList(Arrays.asList(annotation.roles()));

        // 创建认证对象,把自定义用户作为principal
        Authentication auth = new UsernamePasswordAuthenticationToken(
                customUser, null, AuthorityUtils.createAuthorityList(annotation.roles()));
        context.setAuthentication(auth);
        return context;
    }
}

最后在测试类里使用:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class)
@AutoConfigureMockMvc
public class YourControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // 不需要Mock过滤器,注解自动帮我们设置Security上下文
    @Test
    @WithCustomUser(username = "customTestUser", roles = {"ADMIN"})
    void testApiWithCustomUser() throws Exception {
        mockMvc.perform(get("/your/api/endpoint"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("customTestUser")));
    }
}
方案3:生成有效JWT令牌,真实测试过滤器逻辑

如果需要验证JWT过滤器的实际校验逻辑(比如令牌解析、过期校验等),可以直接生成一个有效的令牌,请求时携带:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NfaBackendApplication.class)
@AutoConfigureMockMvc
public class YourControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // 注入项目里的JWT工具类,用来生成有效令牌
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Test
    void testApiWithValidJwtToken() throws Exception {
        // 生成测试用的有效令牌
        String validToken = jwtTokenUtil.generateToken("testUser");

        // 请求时携带Authorization头
        mockMvc.perform(get("/your/api/endpoint")
                        .header("Authorization", "Bearer " + validToken))
                .andExpect(status().isOk());
    }
}
关键注意事项
  • @MockBean替换过滤器时,要确保它不会影响其他依赖该过滤器的Bean,如果有依赖问题,可以考虑在测试配置里临时禁用过滤器;
  • Spring Boot 2.0中Security上下文是线程绑定的,记得在@AfterEach里调用SecurityContextHolder.clearContext()清理,避免测试间数据污染;
  • @AuthenticationPrincipal默认会取Authentication对象的principal属性,所以只要你设置的认证对象的principal是对应的用户类型,就能自动注入。

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

火山引擎 最新活动