【问题标题】:Spring WebFlux, Security and request bodySpring WebFlux、安全性和请求体
【发布时间】:2021-06-23 14:25:42
【问题描述】:

我需要使用请求正文的 HMAC 来保护通过 Spring Boot、WebFlux 和 spring security 实现的 REST API。稍微简化一下,在高层次上 - 请求带有具有请求正文散列值的标头,因此我必须读取标头,读取正文,计算正文的哈希并与标头值进行比较。

我认为我应该实现ServerAuthenticationConverter,但到目前为止我能找到的所有示例都只查看请求标头,而不是正文,我不确定我是否可以只阅读正文,或者我应该包装/用缓存的主体改变请求,以便它可以被底层组件第二次使用?

是否可以使用以下内容:

public class HttpHmacAuthenticationConverter implements ServerAuthenticationConverter {

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
        exchange.getRequest().getBody()
                .next()
                .flatMap(dataBuffer -> {
                    try {
                        return Mono.just(StreamUtils.copyToString(dataBuffer.asInputStream(), StandardCharsets.UTF_8));
                    } catch (IOException e) {
                        return Mono.error(e); 
                    }
                })
                ...


我在 copyToString 行收到来自 IDE 的警告:不适当的阻塞方法调用

任何指南或示例?

谢谢!

我也试过了:

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
        return Mono.justOrEmpty(exchange.getRequest().getHeaders().toSingleValueMap())
                .zipWith(exchange.getRequest().getBody().next()
                        .flatMap(dataBuffer -> Mono.just(dataBuffer.asByteBuffer().array()))
                )
                .flatMap(tuple -> create(tuple.getT1(), tuple.getT2()));

但这不起作用 - 最后一行的 create() 方法中的代码永远不会执行。

【问题讨论】:

    标签: spring spring-security spring-webflux


    【解决方案1】:

    我让它工作。发布我的代码以供参考。

    需要两个组件才能使其工作 - WebFilter 可以读取和缓存请求正文,以便可以多次使用它,而 ServerAuthenticationConverter 将计算正文的哈希并验证签名。

    public class HttpRequestBodyCachingFilter implements WebFilter {
    private static final byte[] EMPTY_BODY = new byte[0];
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // GET and DELETE don't have a body
        HttpMethod method = exchange.getRequest().getMethod();
        if (method == null || method.matches(HttpMethod.GET.name()) || method.matches(HttpMethod.DELETE.name())) {
            return chain.filter(exchange);
        }
    
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .map(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    return bytes;
                })
                .defaultIfEmpty(EMPTY_BODY)
                .flatMap(bytes -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Nonnull
                        @Override
                        public Flux<DataBuffer> getBody() {
                            if (bytes.length > 0) {
                                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
                                return Flux.just(dataBufferFactory.wrap(bytes));
                            }
                            return Flux.empty();
                        }
                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                });
    }
    

    }

    public class HttpJwsAuthenticationConverter implements ServerAuthenticationConverter {
    private static final byte[] EMPTY_BODY = new byte[0];
    
    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .map(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    return bytes;
                })
                .defaultIfEmpty(EMPTY_BODY)
                .flatMap(body -> create(
                        exchange.getRequest().getMethod(),
                        getFullRequestPath(exchange.getRequest()),
                        exchange.getRequest().getHeaders(),
                        body)
                );
    }
    

    ...

    Converter 中的create 方法实现了基于请求方法、路径、标头和正文验证签名的逻辑。如果成功则返回Authentication 的实例,否则返回Mono.empty()

    接线是这样完成的:

    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange().pathMatchers(PATH_API).authenticated()
          ...
          .and()
          .addFilterBefore(new HttpRequestBodyCachingFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
          .addFilterAt(jwtAuthenticationFilter(...), SecurityWebFiltersOrder.AUTHENTICATION);
    }
    
    private AuthenticationWebFilter jwtAuthenticationFilter(ReactiveAuthenticationManager authManager) {
        AuthenticationWebFilter authFilter = new AuthenticationWebFilter(authManager);
        authFilter.setServerAuthenticationConverter(new HttpJwsAuthenticationConverter());
        authFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(PATH_API));
        return authFilter;
    }
    
    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager() {
        return Mono::just;
    }
    
       
    }
    

    【讨论】:

      猜你喜欢
      • 2020-10-17
      • 2020-11-18
      • 2019-02-05
      • 2017-12-20
      • 1970-01-01
      • 1970-01-01
      • 2021-11-03
      • 2023-03-05
      • 2017-12-27
      相关资源
      最近更新 更多