Как отвечать на пользовательское тело json на неавторизованные запросы при реализации настраиваемого диспетчера аутентификации в webflux
Я пытался реализовать пользовательскую аутентификацию токена 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, и я получил результат ниже, и это здорово.
Но когда я ударил его пустым токеном jwt, я получил это.
Теперь я хотел бы вернуть то же тело, которое я возвращаю в случае недействительного токена JWT. но проблема в том, что когда предоставляется пустой токен, он даже не попадает в метод дескриптора класса ExceptionHandler. вот почему это не в моей власти, как я сделал для JwtException в том же классе. Как я могу это сделать, пожалуйста, помогите?
1 ответ
Сам разбираюсь. 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
)
);