Spring Security Webflux / Реактивная обработка исключений


person Mistletoe    schedule 24.12.2017    source источник


Ответы (3)


Решение, которое я нашел, - это создание компонента, реализующего ErrorWebExceptionHandler. Экземпляры bean-компонента ErrorWebExceptionHandler запускаются до фильтров 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, это немного отличается, но идея та же.

ОБНОВЛЕНИЕ: для полноты, вот мой объект 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]);
                }
            }));
    }
}
person MuratOzkan    schedule 23.03.2018
comment
Как в этом случае получить DataBufferWritter? - person Thomas; 03.09.2018
comment
httpResponse.writeWith был единственным вариантом, который работал у меня, позволяя настраивать сообщение на уровне конфигурации. - person Ermintar; 04.07.2019
comment
это правильный ответ, вы можете найти полный пример кода в github.com/eriknyk/ webflux-jwt-security-demo - person eriknyk; 09.04.2021
comment
Я попробовал это, и вот ошибка CORS, которую я получаю: Доступ к XMLHttpRequest на 'localhost: 8084 / users / files 'from origin' localhost: 4200 'заблокирован политикой CORS: ответ на предварительный запрос не проходит проверка контроля доступа: на запрошенном ресурсе отсутствует заголовок Access-Control-Allow-Origin. - person user3648235; 14.06.2021

Нет необходимости регистрировать какой-либо компонент и изменять поведение 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))
        }
    

    }

Проблема: если authMono будет содержать error вместо Authentication - spring вернет HTTP-ответ со статусом 500 (что означает "неизвестная внутренняя ошибка") вместо 401. Даже ошибка AuthenticationException или его подкласса - это не имеет смысла - Spring вернет 500.

Но для нас ясно: исключение AuthenticationException должно вызывать ошибку 401 ...

Чтобы решить эту проблему, мы должны помочь Spring преобразовать исключение в код состояния HTTP-ответа.

Для этого мы можем просто использовать соответствующий класс Exception: 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)
                    )
                ;
            )
        }
   }
person user1726919    schedule 27.06.2018

Я просто просмотрел много документации, столкнувшись с аналогичной проблемой.

Мое решение использовало ResponseStatusException. AccessException Spring-security вроде бы понятен.

.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");})
      ;

Если это идет в правильном для вас направлении, я могу предоставить немного лучший образец.

person Frischling    schedule 13.02.2018
comment
Это, безусловно, самое простое решение, и оно мне очень нравится. Спасибо! - person Fre_d; 23.01.2019
comment
Как сопоставить любое общее исключение, не полагаясь на ControllerAdvice? baeldung.com/spring-webflux-errors Я пробовал это, но это не помогло работа с весенней загрузкой webflux 2.3.2.RELEASE - person Sergio Bilello; 07.08.2020
comment
Извините, не знаю, но думаю, стоит задать еще один вопрос ... - person Frischling; 17.08.2020