Spring 安全性 Webflux/Reactive 异常处理
Spring Security Webflux/Reactive Exception Handling
我正在 spring webflux 上构建应用程序,但我被卡住了,因为 spring security webflux (v.M5) 在异常方面不像 Spring 4处理。
我看到以下 post 关于如何自定义 spring 安全 webflux:
如果我们在 ServerSecurityContextRepository.load 中抛出异常,Spring 会将 http header 更新为 500,而我无法处理此异常。
但是,控制器中抛出的任何错误都可以使用常规的@ControllerAdvice 来处理,它只是 spring webflux 安全性。
在spring webflux security 中是否有处理异常的方法?
我刚刚查阅了很多文档,遇到了类似的问题。
我的解决方案是使用 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");})
;
如果这对您来说方向正确,我可以提供更好的示例。
我找到的解决方案是创建一个实现 ErrorWebExceptionHandler
的组件。在 Spring 安全过滤器之前 ErrorWebExceptionHandler
bean 运行 的实例。这是我使用的示例:
@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);
}
}
如果您注入的是 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]);
}
}));
}
}
无需注册任何 bean 和更改默认 Spring 行为。尝试更优雅的解决方案:
我们有:
- ServerSecurityContextRepository 的自定义实现
方法.load return 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))
}
}
问题是: 如果 authMono
将包含 error
而不是 Authentication
- spring 将 return 具有 500 状态(这意味着 "an unknown internal error")而不是 401 的 http 响应。即使错误是 AuthenticationException 或者它是 subclass - 它没有意义 - Spring 将 return 500.
但我们很清楚:AuthenticationException 应该产生 401 错误...
要解决这个问题,我们必须帮助Spring 如何将异常转换为 HTTP 响应状态代码。
要做到这一点,我们只需使用适当的异常 class: 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)
)
;
)
}
}
我正在 spring webflux 上构建应用程序,但我被卡住了,因为 spring security webflux (v.M5) 在异常方面不像 Spring 4处理。
我看到以下 post 关于如何自定义 spring 安全 webflux:
如果我们在 ServerSecurityContextRepository.load 中抛出异常,Spring 会将 http header 更新为 500,而我无法处理此异常。
但是,控制器中抛出的任何错误都可以使用常规的@ControllerAdvice 来处理,它只是 spring webflux 安全性。
在spring webflux security 中是否有处理异常的方法?
我刚刚查阅了很多文档,遇到了类似的问题。
我的解决方案是使用 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");})
;
如果这对您来说方向正确,我可以提供更好的示例。
我找到的解决方案是创建一个实现 ErrorWebExceptionHandler
的组件。在 Spring 安全过滤器之前 ErrorWebExceptionHandler
bean 运行 的实例。这是我使用的示例:
@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);
}
}
如果您注入的是 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]);
}
}));
}
}
无需注册任何 bean 和更改默认 Spring 行为。尝试更优雅的解决方案:
我们有:
- ServerSecurityContextRepository 的自定义实现
方法.load return 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)) }
}
问题是: 如果 authMono
将包含 error
而不是 Authentication
- spring 将 return 具有 500 状态(这意味着 "an unknown internal error")而不是 401 的 http 响应。即使错误是 AuthenticationException 或者它是 subclass - 它没有意义 - Spring 将 return 500.
但我们很清楚:AuthenticationException 应该产生 401 错误...
要解决这个问题,我们必须帮助Spring 如何将异常转换为 HTTP 响应状态代码。
要做到这一点,我们只需使用适当的异常 class: 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)
)
;
)
}
}