使用承载令牌(JWT)和自定义声明进行Webflux安全授权测试



我有一个充当资源服务器的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();
}
}

我一直在努力遵循这个指南我已经尝试过使用和不使用.filter(basicAuthentication())的客户端,以防万一:(

在我看来,mockJwt()没有被放入请求Authorization头字段中。

我还认为,被注入到我的ReactiveAuthorizationManager中的ReactiveJwtDecoder将试图根据身份提供者解码测试JWT,这将失败。

我可以模拟ReactiveAuthorizationManagerReativeJwtDecoder

我缺什么了吗?也许有一种方法可以创建";测试";使用身份服务JWK集合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();
}
}

粗略地说,我实际所做的是使用自定义声明作为"权威";,也就是说";myClaim"必须包含值"0";x〃;以允许访问给定路径。这与声明有点不同,声明是一个简单的自定义声明,即令牌中的额外数据位(可能是用户喜欢的配色方案(
考虑到这一点,我意识到我在测试中观察到的行为可能是正确的,所以我选择配置ReactiveJwtAuthenticationConverter:,而不是实现ReactiveAuthorizationManager

@Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
final JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix("");       // 1
converter.setAuthoritiesClaimName("myClaim");
final Converter<Jwt, Flux<GrantedAuthority>> rxConverter = new ReactiveJwtGrantedAuthoritiesConverterAdapter(converter);
final ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(rxConverter);
return jwtAuthenticationConverter;
}

(注释1;JwtGrantedAuthoritiesConverter在索赔值前加上"SCOPE_",这可以使用setAuthorityPrefix进行控制,请参见(

这需要对SecurityWebFilterChain配置进行调整:

http
.csrf().disable()
.logout().disable()
.authorizeExchange().pathMatchers("securedEndpoint").hasAnyAuthority("myValue)
.anyExchange().permitAll()
.and()
.oauth2ResourceServer()
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter));

测试

@SpringBootTest
class ControllerTest {
private WebTestClient webTestClient;
@Autowired
public void setUp(final ApplicationContext applicationContext) {
webTestClient = WebTestClient
.bindToApplicationContext(applicationContext) // 2
.apply(springSecurity())  // 3
.configureClient()
.build();
}
@Test
void myTest() {
webTestClient
.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("myValue")))  // 4
.build()
.get()
.uri("/securedEndpoint")
.exchange()
.expectStatus()
.isOk()
} 
}

为了进行测试";似乎CCD_ 17需要绑定到应用程序上下文(在注释2(
理想情况下,我更喜欢将WebTestClient绑定到服务器,但是,当使用bindToServer时,apply(springSecurity())(注释3(不会为apply返回合适的类型

有许多不同的方式来";mock";测试时的JWT,(在注释4中(用于替代方案的JWT参见此处的春季文档

我希望这能在未来帮助其他人,安全性和OAuth2可能会令人困惑:(

感谢@Toerktumlare为我指明了有用文档的方向。

相关内容

  • 没有找到相关文章