Spring Security 如何在Sping Boot 中支持不同的Bearer令牌类型?

4urapxun  于 11个月前  发布在  Spring
关注(0)|答案(1)|浏览(205)

我们需要构建一个支持传统定制(而不是JWT)承载令牌和认证头的服务:

Authorization: bespoke ....

字符串
JWT Bearer token Auth header:

Authorization: Bearer ......


在Sping Boot 3.2中,使用Webflux和oauth2-resource-server实现Bean的最干净的方法是什么?
我希望有一个自定义bean来组成DefaultBearerTokenResolver和一个定制的解析器,或者一个自定义ReactiveAuthenticationManagerResolver bean来做同样的事情,但是我不确定哪一个更简单。

5sxhfpxr

5sxhfpxr1#

如果两者之间的安全配置明显不同,您应该定义不同的SecurityWebFilterChain bean:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityWebFilterChain bespokeFilterChain(ServerHttpSecurity http) {
    http.securityMatcher((ServerWebExchange exchange) -> {
        final var isBespoke = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)).map(auth -> auth.toLowerCase().startsWith("bespoke ") ? true : false).orElse(false);
        return isBespoke ? ServerWebExchangeMatcher.MatchResult.match() : ServerWebExchangeMatcher.MatchResult.notMatch();
    });
    ...
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
SecurityWebFilterChain bearerFilterChain(ServerHttpSecurity http) {
    http.securityMatcher((ServerWebExchange exchange) -> {
        final var isBearer = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)).map(auth -> auth.toLowerCase().startsWith("bearer ") ? true : false).orElse(false);
        return isBearer ? ServerWebExchangeMatcher.MatchResult.match() : ServerWebExchangeMatcher.MatchResult.notMatch();
    });
    ...
}

...

@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
SecurityWebFilterChain defaultFilterChain(ServerHttpSecurity http) {
    // No http.securityMatcher here
    // Processes requests which were not matched in filter chains with higher precedence (for instance anonymous requests)
    ...
}

字符串
这里我演示了Authorization头的匹配器,但是你可以匹配请求中的任何内容(路径,媒体类型,源IP等)。
当不同身份验证策略之间的安全要求不同时,这非常有用,例如,当混合在单个应用程序中时:

  • oauth2Login():需要会话、CSRF保护,通常配置为302 redirect to login,用于对受保护资源的未经授权请求
  • oauth2ResourceServer():通常无状态(既没有会话也没有CSRF保护),使用401 unauthorized

如果除了身份验证管理器之外的所有内容都是相同的,那么您可能应该实现自己的ReactiveAuthenticationManagerResolver<ServerWebExchange>,而不是编写几乎相同的过滤器链bean:

@Bean
SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver) {
    http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
    ...
}

@Component
static class MyReactiveAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver<ServerWebExchange> {
    private final ReactiveAuthenticationManager bespokeAuthManager;
    private final ReactiveAuthenticationManager bearerAuthManager;
    private final ReactiveAuthenticationManager defaultAuthManager;
    
    MyReactiveAuthenticationManagerResolver() {
        ...
    }

    @Override
    public Mono<ReactiveAuthenticationManager> resolve(ServerWebExchange context) {
        final var authHeader = Optional.ofNullable(context.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)).map(auth -> auth.toLowerCase()).orElse("");
        if(authHeader.startsWith("bespoke ")) {
            return Mono.just(bespokeAuthManager);
        }
        if(authHeader.startsWith("bearer ")) {
            return Mono.just(bearerAuthManager);
        }
        return Mono.just(defaultAuthManager);
    }
}

相关问题