Как ответить custom json body на неавторизованные запросы при реализации настраиваемого менеджера аутентификации в webflux - PullRequest
0 голосов
/ 06 августа 2020

Я пытался реализовать пользовательскую аутентификацию токена JWT, в то время как я также обрабатываю глобальные исключения, чтобы настроить тело ответа для каждого типа исключений. Все работает нормально, за исключением того, что я хотел бы вернуть пользовательский ответ json при получении неавторизованного запроса вместо кода состояния 401.

Ниже представлена ​​моя реализация для JwtServerAuthenticationConverter и JwtAuthenticationManager.

@Component
public class JwtServerAuthenticationConverter implements ServerAuthenticationConverter {

    private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer ";

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {

        return Mono.justOrEmpty(exchange)
                .flatMap(serverWebExchange -> Mono.justOrEmpty(
                        serverWebExchange
                                .getRequest()
                                .getHeaders()
                                .getFirst(HttpHeaders.AUTHORIZATION)
                        )
                )
                .filter(header -> !header.trim().isEmpty() && header.trim().startsWith(AUTH_HEADER_VALUE_PREFIX))
                .map(header -> header.substring(AUTH_HEADER_VALUE_PREFIX.length()))
                .map(token -> new UsernamePasswordAuthenticationToken(token, token))
                ;
    }
}
@Component
public class JwtAuthenticationManager implements ReactiveAuthenticationManager {

    private final JWTConfig jwtConfig;
    private final ObjectMapper objectMapper;

    public JwtAuthenticationManager(JWTConfig jwtConfig, ObjectMapper objectMapper) {
        this.jwtConfig = jwtConfig;
        this.objectMapper = objectMapper;
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {

        return Mono.just(authentication)
                .map(auth -> JWTHelper.loadAllClaimsFromToken(auth.getCredentials().toString(), jwtConfig.getSecret()))
                .onErrorResume(throwable -> Mono.error(new JwtException("Unauthorized")))
                .map(claims -> objectMapper.convertValue(claims, JWTUserDetails.class))
                .map(jwtUserDetails ->
                        new UsernamePasswordAuthenticationToken(
                                jwtUserDetails,
                                authentication.getCredentials(),
                                jwtUserDetails.getGrantedAuthorities()
                        )
                )
                ;
    }
}

И ниже моя глобальная обработка исключений, которая работает абсолютно нормально, за исключением случая, когда webflux возвращает 401 из метода преобразования JwtServerAuthenticationConverter.

@Configuration
@Order(-2)
public class ExceptionHandler implements WebExceptionHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

        exchange.getResponse().getHeaders().set("Content-Type", MediaType.APPLICATION_JSON_VALUE);

        return buildErrorResponse(ex)
                .flatMap(
                        r -> r.writeTo(exchange, new HandlerStrategiesResponseContext(HandlerStrategies.withDefaults()))
                );
    }

    private Mono<ServerResponse> buildErrorResponse(Throwable ex) {

        if (ex instanceof RequestEntityValidationException) {

            return ServerResponse.badRequest().contentType(MediaType.APPLICATION_JSON).body(
                    Mono.just(new ErrorResponse(ex.getMessage())),
                    ErrorResponse.class
            );

        } else if (ex instanceof ResponseStatusException) {
            ResponseStatusException exception = (ResponseStatusException) ex;

            if (exception.getStatus().value() == 404) {
                return ServerResponse.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(
                        Mono.just(new ErrorResponse("Resource not found - 404")),
                        ErrorResponse.class
                );
            } else if (exception.getStatus().value() == 400) {
                return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON).body(
                        Mono.just(new ErrorResponse("Unable to parse request body - 400")),
                        ErrorResponse.class
                );
            }

        } else if (ex instanceof JwtException) {

            return ServerResponse.status(HttpStatus.UNAUTHORIZED).contentType(MediaType.APPLICATION_JSON).body(
                    Mono.just(new ErrorResponse(ex.getMessage())),
                    ErrorResponse.class
            );
        }

        ex.printStackTrace();
        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON).body(
                Mono.just(new ErrorResponse("Internal server error - 500")),
                ErrorResponse.class
        );

    }
}

@RequiredArgsConstructor
class HandlerStrategiesResponseContext implements ServerResponse.Context {

    private final HandlerStrategies handlerStrategies;

    @Override
    public List<HttpMessageWriter<?>> messageWriters() {
        return this.handlerStrategies.messageWriters();
    }

    @Override
    public List<ViewResolver> viewResolvers() {
        return this.handlerStrategies.viewResolvers();
    }
}

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(
            ServerHttpSecurity http,
            ReactiveAuthenticationManager jwtAuthenticationManager,
            ServerAuthenticationConverter jwtAuthenticationConverter
    ) {

        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(jwtAuthenticationManager);
        authenticationWebFilter.setServerAuthenticationConverter(jwtAuthenticationConverter);

        return http
                .authorizeExchange()
                .pathMatchers("/auth/login", "/auth/logout").permitAll()
                .anyExchange().authenticated()
                .and()
                .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .httpBasic()
                .disable()
                .csrf()
                .disable()
                .formLogin()
                .disable()
                .logout()
                .disable()
                .build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Итак, когда я нажимаю на него недействительным Токен JWT в заголовке. Этим занимался мой класс ExceptioHandler, и я получил результат ниже, и это здорово. enter image description here

But when i hit it with empty jwt token I got this.

введите описание изображения здесь

Теперь я хотел бы вернуть то же тело, которое я возвращаю в случае недействительного токена JWT. но проблема в том, что когда предоставляется пустой токен, он даже не попадает в метод дескриптора класса ExceptionHandler. вот почему это не в моей власти, как я сделал для JwtException в том же классе. Как я могу это сделать, помогите, пожалуйста?

1 Ответ

0 голосов
/ 07 августа 2020

Сам разбираюсь. webflux предоставляет ServerAuthenticationFailureHandler для обработки пользовательского ответа для этого, но, к сожалению, ServerAuthenticationFailureHandler не работает, и это известная проблема, поэтому я создал маршрут отказа и написал в нем свой собственный ответ и настроил страницу входа.

.formLogin()
.loginPage("/auth/failed")
.and()
.andRoute(path("/auth/failed").and(accept(MediaType.APPLICATION_JSON)), (serverRequest) ->
        ServerResponse
                .status(HttpStatus.UNAUTHORIZED)
                .body(
                        Mono.just(new ErrorResponse("Unauthorized")),
                        ErrorResponse.class
                )
);
...