使用 WebTestClient 在 @SpringBootTest 中模拟 JWT 令牌

Mocking JWT token in @SpringBootTest with WebTestClient

我在 Spring 引导项目中有以下控制器 class:

@GetMapping
public ResponseEntity<UserResponse> getUser(@AuthenticationPrincipal CustomUserDetails userDetails) {
    try {
        final UserResponse userData = userService.getUser(userDetails.getId());
        return ResponseEntity.ok(userData);
    } catch (UserNotFoundException e) {
        log.error("User with id {} not found", userDetails.getId());
        return ResponseEntity.notFound().build();
    }
}

仅当客户端使用 Authorization: Bearer <token> 发送 JWT 令牌时才能访问此资源。在通过 JwtRequestFilter.

解析 JWT 令牌后,CustomUserDetailsCustomUserDetailsService 提供

现在我想编写一个 @SpringBootTest,它使用调用此资源的真实 HTTP 客户端。我的第一个想法是使用 MockMvc 对象,但后来我读到了 Spring.

提供的 WebTestClient

但是我还不明白如何模拟 JWT 令牌。这是我的初步尝试:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
public class UserControllerIT {

    @Autowired
    private ApplicationContext context;

    private WebTestClient webTestClient;

    @MockBean
    private UserRepo userRepo;

    @BeforeEach
    public void setup() {
        webTestClient = WebTestClient
                .bindToApplicationContext(context)
                .apply(springSecurity())
                .configureClient()
                .build();
    }

    @Test
    @WithMockUser
    public void someTest() {
        final User user = createUser("foo@bar.com", "my-password");

        when(userRepo.findById(anyLong())).thenReturn(Optional.of(user));

        webTestClient
                .mutateWith(mockJwt())
                .get()
                .uri("/user")
                .header(ACCEPT, APPLICATION_JSON_VALUE)
                .exchange()
                .expectStatus().is2xxSuccessful();
    }

此测试失败,出现以下异常:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'webHandler' available

但是我不确定我的方法是否有意义。 WebTestClient 是“正确”的方式吗?或者我需要使用 WebClient?

我的安全配置:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final JwtRequestFilter jwtRequestFilter;

    public SecurityConfiguration(JwtRequestFilter jwtRequestFilter) {
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(final HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors().and()
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
                .mvcMatcher("/services/**").authorizeRequests()
                .mvcMatchers(PUBLIC_RESOURCES).permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

WebTestClientTestRestTemplate 的推荐替代品。

https://github.com/spring-projects/spring-security 深入研究 Spring 安全源,展示了一些将 WebTestClient 与 JWT 结合使用的示例。例如:ServerOAuth2ResourceServerApplicationITests

鉴于您有一个服务 JwtTokenProvider 负责生成 JWT 令牌,测试可能如下所示。或者,如果可能,您可以使用 ServerOAuth2ResourceServerApplicationITests.

中的常量标记
package no.yourcompany.yourapp.yourpackage;

import no.yourcompany.yourapp.configuration.JwtTokenProvider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
public class YourIntegrationTest {

    @Autowired
    WebTestClient webTestClient;

    @Autowired
    JwtTokenProvider jwtTokenProvider;

    @Test
    public void postTest_withValidToken_receiveOk() {

        var tokenString = jwtTokenProvider.createToken(
                new UsernamePasswordAuthenticationToken("test-user", "P4ssword"));

        webTestClient
                .post().uri("/test")
                .headers(http -> http.setBearerAuth(tokenString))
                .exchange()
                .expectStatus().isOk();
    }
}

对于 WebTestClient,将其添加到 POM

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <scope>test</scope>
</dependency>