Spring webfluxでアプリを作成していますが、Spring Security Webflux(v.M5)が例外処理の点でSpring 4のように動作しなかったために行き詰まっています。
Spring Security WebFluxをカスタマイズする方法について次の投稿を見ました: APIのSpring WebFluxカスタム認証
ServerSecurityContextRepository.loadで例外をスローすると、Springはhttpヘッダーを500に更新し、この例外を操作するためにできることは何もありません。
ただし、コントローラーでスローされたエラーは通常の@ControllerAdviceを使用して処理でき、webfluxのセキュリティを強化するだけです。
春のwebfluxセキュリティで例外を処理する方法はありますか?
私が見つけた解決策は、ErrorWebExceptionHandler
を実装するコンポーネントを作成することです。 ErrorWebExceptionHandler
Beanのインスタンスは、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);
}
}
代わりにHttpHandler
を注入する場合は少し異なりますが、考え方は同じです。
PDATE:完全を期すために、ここに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]);
}
}));
}
}
たくさんのドキュメントを調べたところ、同様の問題が発生しました。
私のソリューションは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");})
;
これが正しい方向に進んだら、もう少し良いサンプルを提供できます。
Beanを登録したり、デフォルトのSpring動作を変更したりする必要はありません。代わりに、よりエレガントなソリューションを試してください:
私たちは:
メソッド.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
が含まれる場合-スプリングは500ステータスでhttp応答を返します(「不明な内部エラー」を意味します)401の代わりに。エラーもAuthenticationExceptionまたはそれのサブクラスです-意味がありません-Springは500を返します。
しかし、それは私たちにとって明らかです:AuthenticationExceptionは401エラーを生成するはずです...
この問題を解決するには、例外をHTTP応答ステータスコードに変換する方法をSpringで支援する必要があります。
これを行うには、適切な例外クラス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)
)
;
)
}
}
_