【发布时间】:2021-01-06 10:21:18
【问题描述】:
我有一个充当资源服务器的 Spring Boot (2.3.6.RELEASE) 服务,它已使用 Webflux 实现,客户端 jwt 由第三方身份服务器提供。
我正在尝试使用 JUnit 5 和@SpringBootTest 测试端点的安全性。 (据记录,在手动测试期间,安全性似乎可以按要求工作)
我正在改变 WebTestClient 以包含一个带有适当声明 (myClaim) 的 JWT,但是在我的自定义 ReactiveAuthorizationManager 中,请求标头中没有承载令牌,因此无需解码或声明验证请求授权失败,这是应该的。
因此,我的测试设置是:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class ControllerTest {
@Autowired
private ApplicationContext applicationContext;
private WebTestClient webTestClient;
@BeforeEach
void init() {
webTestClient = WebTestClient
.bindToApplicationContext(applicationContext)
.apply(springSecurity())
.configureClient()
.build();
}
@Test
void willAllowAccessForJwtWithValidClaim() {
webTestClient.mutateWith(mockJwt().jwt(jwt -> jwt.claim("myClaim", "{myValue}")))
.get()
.uri("/securedEndpoint")
.exchange()
.expectStatus()
.isOk();
}
}
我一直在尝试关注这个guide
为了以防万一,我已经尝试过使用和不使用.filter(basicAuthentication()) 的客户端:)
在我看来,mockJwt() isint 被放入请求 Authorization 标头字段中。
我还认为注入我的ReactiveAuthorizationManager 的ReactiveJwtDecoder 将尝试针对身份提供者解码测试JWT,这将失败。
我可以模拟ReactiveAuthorizationManager 或ReativeJwtDecoder。
我有什么遗漏吗? 也许有一种方法可以使用 Identity Services JWK set uri 创建“测试”JWT?
其他细节:
ReactiveAuthorizationManager 和安全配置的详细信息
public class MyReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private static final AuthorizationDecision UNAUTHORISED = new AuthorizationDecision(false);
private final ReactiveJwtDecoder jwtDecoder;
public JwtRoleReactiveAuthorizationManager(final ReactiveJwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
@Override
public Mono<AuthorizationDecision> check(final Mono<Authentication> authentication, final AuthorizationContext context) {
final ServerWebExchange exchange = context.getExchange();
if (null == exchange) {
return Mono.just(UNAUTHORISED);
}
final List<String> authorisationHeaders = exchange.getRequest().getHeaders().getOrEmpty(HttpHeaders.AUTHORIZATION);
if (authorisationHeaders.isEmpty()) {
return Mono.just(UNAUTHORISED);
}
final String bearer = authorisationHeaders.get(0);
return jwtDecoder.decode(bearer.replace("Bearer ", ""))
.flatMap(jwt -> determineAuthorisation(jwt.getClaimAsStringList("myClaim")));
}
private Mono<AuthorizationDecision> determineAuthorisation(final List<String> claimValues) {
if (Objects.isNull(claimValues)) {
return Mono.just(UNAUTHORISED);
} else {
return Mono.just(new AuthorizationDecision(!Collections.disjoint(claimValues, List.of("myValues")));
}
}
}
@EnableWebFluxSecurity
public class JwtSecurityConfig {
@Bean
public SecurityWebFilterChain configure(final ServerHttpSecurity http,
final ReactiveAuthorizationManager reactiveAuthorizationManager) {
http
.csrf().disable()
.logout().disable()
.authorizeExchange().pathMatchers("/securedEndpoint").access(reactiveAuthorizationManager)
.anyExchange().permitAll()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
【问题讨论】:
-
.bindToApplicationContext(applicationContext)用于当你有一个模拟的ApplicationContext另一方面你似乎正在运行一个完整的服务器@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)这意味着你应该使用bindToServer()而不是docs.spring.io/spring-framework/docs/current/javadoc-api/org/…跨度> -
或者你省略
bindTo...,它将默认配置docs.spring.io/spring-boot/docs/current/reference/htmlsingle/… -
@Toerktumlare 感谢 cmets :) 有趣的是,如果我尝试使用 bindToServer 我不能
apply(springSecurity()它的类型错误,但值得在这里更深入地玩一下。如果我自动连接WebTestClient,我会得到NullPointerException。我怀疑存在错误,或者功能尚不存在,因为我还没有看到任何有关处理自定义声明的文档。 -
@Toerktumlare 谢谢你,这很有帮助。当有这么多不同的解决方案时,很难知道正确的方法。我会看看
GrantedAuthoritiesMapper和JwtAuthenticationConverter,这可能更适合我的用例。 -
@WUJ 有趣的想法。我不知道 JWSSigner。我想我现在已经整理好了(著名的遗言),但我肯定会检查 JWSSigner,它可能在未来证明是有用的,谢谢 :)
标签: testing jwt spring-webflux spring-security-oauth2