我正在尝试实现一个 AuthorizationHandlerAdapter (实现
org.springframework.web.reactive.HandlerAdapter
接口)。它调用外部端点来获取用户的权限。
@Component
@Slf4j
public class AuthorisationHandlerAdapter implements HandlerAdapter {
@Autowired
RequestMappingHandlerAdapter handlerAdapter;
@Autowired
AuthWebClient authWebClient;
@Override
public boolean supports(Object handler) {
return true;
}
@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
return authorizeInternal(exchange, ImmutableList.of("user.view"))
.then(handlerAdapter.handle(exchange, handler));
}
protected Mono<Void> authorizeInternal(ServerWebExchange exchange, List<String> requiredPermission) {
HttpHeaders headers = exchange.getRequest().getHeaders();
String sub = headers.get("sub").get(0);
String auth = Objects.requireNonNull(headers.get("Authorization")).get(0); //remove todo
return authWebClient.getAuthorities(sub, auth)
.flatMap(authorities -> {
List<String> serviceAuthorities = authorities.getPermissions();
if (!serviceAuthorities.containsAll(requiredPermission)) {
return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "Forbidden access"));
}
return Mono.when();
})
;
}
此 Web 客户端调用外部端点:
@Service
@Slf4j
public class AuthWebClient {
@Autowired
private WebClient webClient;
public Mono<Authorities> getAuthorities(String subject, String auth) {
URI uri = UriComponentsBuilder.fromUriString(-someUrl-).toUri();
Authorities authoritiesRequest = Authorities.builder().includePermissions(true).build();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", auth);
return webClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.body(BodyInserters.fromValue(authoritiesRequest))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(AuthoritiesResponse.class);
}
}
但是,当外部端点发送 401 未授权状态时,系统会将 401 错误包装为 500。我需要将 401 状态传播给用户。可能我遗漏了一些点。
以下堆栈跟踪打印在控制台上:
2022-05-19 19:03:20.229 ERROR 58468 --- [ctor-http-nio-4] a.w.r.e.AbstractErrorWebExceptionHandler : [addd567f-2] 500 Server Error for HTTP GET "/org-services/org"
org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from POST https://someUrl/subjects/6d0b-asdf-asd-f2323/permissions
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 401 from POST https://someUrl/subjects/6d0b-asdf-asd-f2323/permissions [DefaultWebClient]
*__checkpoint ⇢ -packageName-.api.filter.TokenResolverFilter [DefaultWebFilterChain]
*__checkpoint ⇢ -packageName-.api.filter.UnsuccessfulResponseFilter$$Lambda$1421/415917546 [DefaultWebFilterChain]
*__checkpoint ⇢ -packageName-.api.config.CorsConfiguration$$Lambda$1420/297366988 [DefaultWebFilterChain]
*__checkpoint ⇢ -packageName-.api.filter.GzipDecompressionFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP GET "/***/org/keys
----------
" [ExceptionHandlingWebHandler]
Stack trace:
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198)
at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:207)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:101)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:127)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:127)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onNext(FluxFilterFuseable.java:118)
at -packageName-.api.lifter.MdcContextLifter.onNext(MdcContextLifter.java:37)
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159)
at -packageName-.api.lifter.MdcContextLifter.onComplete(MdcContextLifter.java:48)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at -packageName-.api.lifter.MdcContextLifter.onComplete(MdcContextLifter.java:48)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
at -packageName-.api.lifter.MdcContextLifter.onComplete(MdcContextLifter.java:48)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400)
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419)
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:473)
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1372)
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1235)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1284)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:750)