Spring Security Webflux/Reactive异常处理

Mis*_*toe 9 spring-security spring-webflux

我在Spring webflux上构建应用程序,并且因为Spring安全性webflux(v.M5)在异常处理方面的行为不像Spring 4而被卡住了.

我看到以下关于如何定制spring security webflux的帖子: 针对API的Spring webflux自定义身份验证

如果我们在ServerSecurityContextRepository.load中抛出异常,那么Spring会将http标头更新为500,而我无法操作此异常.

但是,控制器中抛出的任何错误都可以使用常规的@ControllerAdvice来处理,它只是弹出webflux安全性.

反正有没有处理spring webflux安全性中的异常?

Fri*_*ing 5

我刚刚查阅了大量文档,遇到了类似的问题。

我的解决方案是使用 ResponseStatusException。Spring-security的AccessException似乎可以理解。

.doOnError(
          t -> AccessDeniedException.class.isAssignableFrom(t.getClass()),
          t -> AUDIT.error("Error {} {}, tried to access {}", t.getMessage(), principal, exchange.getRequest().getURI())) // if an error happens in the stream, show its message
.onErrorMap(
        SomeOtherException.class, 
        t -> { return new ResponseStatusException(HttpStatus.NOT_FOUND,  "Collection not found");})
      ;
Run Code Online (Sandbox Code Playgroud)

如果这对您来说是正确的方向,我可以提供更好的样本。


Mur*_*kan 5

我发现的解决方案是创建一个实现的组件ErrorWebExceptionHandlerErrorWebExceptionHandlerbean 的实例在Spring Security过滤器之前运行。这是我使用的示例:

@Slf4j
@Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

  @Autowired
  private DataBufferWriter bufferWriter;

  @Override
  public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
    HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
    AppError appError = ErrorCode.GENERIC.toAppError();

    if (ex instanceof AppException) {
        AppException ae = (AppException) ex;
        status = ae.getStatusCode();
        appError = new AppError(ae.getCode(), ae.getText());

        log.debug(appError.toString());
    } else {
        log.error(ex.getMessage(), ex);
    }

    if (exchange.getResponse().isCommitted()) {
        return Mono.error(ex);
    }

    exchange.getResponse().setStatusCode(status);
    return bufferWriter.write(exchange.getResponse(), appError);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果要注入HttpHandler,则有点不同,但是想法是相同的。

更新:为了完整DataBufferWriter起见,这是我的对象,它是一个@Component

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class DataBufferWriter {
    private final ObjectMapper objectMapper;

    public <T> Mono<Void> write(ServerHttpResponse httpResponse, T object) {
        return httpResponse
            .writeWith(Mono.fromSupplier(() -> {
                DataBufferFactory bufferFactory = httpResponse.bufferFactory();
                try {
                    return bufferFactory.wrap(objectMapper.writeValueAsBytes(object));
                } catch (Exception ex) {
                    log.warn("Error writing response", ex);
                    return bufferFactory.wrap(new byte[0]);
                }
            }));
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

无需注册任何 bean 并更改默认的 Spring 行为。尝试更优雅的解决方案:

我们有:

  1. ServerSecurityContextRepository 的自定义实现
  2. 方法 .load 返回 Mono

    public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
        ....
        @Override
        public Mono<SecurityContext> load(ServerWebExchange exchange) {
            List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
            String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;
    
            Mono<Authentication> authMono = reactiveAuthenticationManager
                    .authenticate( new HttpRequestHeaderToken(token) );
    
            return authMono
                    .map( auth -> (SecurityContext)new SecurityContextImpl(auth))
        }
    
    Run Code Online (Sandbox Code Playgroud)

    }

问题是:如果authMono遗嘱包含一个error而不是Authentication- spring 将返回带有500状态(这意味着“未知的内部错误”)而不是401的http响应。即使错误是 AuthenticationException 或者它的子类 - 它没有意义 - Spring 将返回 500。

但我们很清楚:AuthenticationException 应该产生 401 错误......

为了解决这个问题,我们必须帮助Spring如何将异常转换为HTTP响应状态码。

为了使我们可以只使用适当的异常类:ResponseStatusException或者仅仅是一个原始异常映射到这一个(例如,通过添加onErrorMap()authMono对象)。看最后的代码:

    public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
        ....
        @Override
        public Mono<SecurityContext> load(ServerWebExchange exchange) {
            List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
            String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;

            Mono<Authentication> authMono = reactiveAuthenticationManager
                    .authenticate( new HttpRequestHeaderToken(token) );

            return authMono
                    .map( auth -> (SecurityContext)new SecurityContextImpl(auth))
                    .onErrorMap(
                            er -> er instanceof AuthenticationException,
                            autEx -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, autEx.getMessage(), autEx)
                    )
                ;
            )
        }
   }
Run Code Online (Sandbox Code Playgroud)