web-dev-qa-db-ja.com

Spring Security Webflux / Reactive Exception Handling

Spring webfluxでアプリを作成していますが、Spring Security Webflux(v.M5)が例外処理の点でSpring 4のように動作しなかったために行き詰まっています。

Spring Security WebFluxをカスタマイズする方法について次の投稿を見ました: APIのSpring WebFluxカスタム認証

ServerSecurityContextRepository.loadで例外をスローすると、Springはhttpヘッダーを500に更新し、この例外を操作するためにできることは何もありません。

ただし、コントローラーでスローされたエラーは通常の@ControllerAdviceを使用して処理でき、webfluxのセキュリティを強化するだけです。

春のwebfluxセキュリティで例外を処理する方法はありますか?

11
Mistletoe

私が見つけた解決策は、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]);
                }
            }));
    }
}
5
MuratOzkan

たくさんのドキュメントを調べたところ、同様の問題が発生しました。

私のソリューションは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");})
      ;

これが正しい方向に進んだら、もう少し良いサンプルを提供できます。

3
Frischling

Beanを登録したり、デフォルトのSpring動作を変更したりする必要はありません。代わりに、よりエレガントなソリューションを試してください:

私たちは:

  1. ServerSecurityContextRepositoryのカスタム実装
  2. メソッド.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))
        }
    _

    }

問題は:authMonoerrorではなく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)
                    )
                ;
            )
        }
   }
_
2
user1726919