Spring Cloud Gateway自定义授权过滤器异常问题排查
问题:Spring Cloud Gateway自定义授权过滤器授权失败仍返回200
我用Spring Cloud Gateway搭建网关,通过自定义过滤器实现路由授权,下游是阻塞式API。过滤器通过JWT令牌调用外部授权API获取用户权限,校验用户是否具备EDIT_ACCESS权限,不满足时预期返回401,但实际授权失败时仍返回200,授权通过则能正常获取下游API响应。
相关代码与报错信息
过滤器代码
@Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0); //API to get permissions Mono<PermissionResponse> permissionsOfUser = getPermissions(authorizationHeader); return permissionsOfUser.flatMap(pu->{ //doing permission check if(!isAuthorized(pu)){ return Mono.error(new AuthorizationException("Invalid")); }else { return chain.filter(exchange); } }) .doOnError(auth->this.onError(exchange,HttpStatus.UNAUTHORIZED)) .doOnNext(auth->chain.filter(exchange)); }; }
权限校验方法
private boolean isAuthorized(PermissionResponse permissionResponse) { if(permissionResponse.getFunctions().stream().map(f->f.getFuncKey()).collect(Collectors.toList()).contains("EDIT_ACCESS")){ log.info("Has Permissions"); return true; }else{ log.info("No Permissions"); return false; } }
获取权限的API调用
private Mono<PermissionResponse> getPermissions(String authorizationHeader) { return webClientBuilder.build() .get() .uri(authPermissionsUrl) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.AUTHORIZATION, authorizationHeader) .retrieve() .bodyToMono(PermissionResponse.class) .subscribeOn(Schedulers.immediate()) .publishOn(Schedulers.immediate()); }
错误响应处理
private Mono<Void> onError(ServerWebExchange exchange, HttpStatus httpStatus) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(httpStatus); return response.setComplete(); }
路由配置
cloud: gateway: default-filters: - name: CustomAuthorizationFilter routes: - id: app-id uri: http://myapi.com predicates: - Path=/getMyApiDetails/**
报错日志
2022-08-26 10:22:03.314 ERROR 16068 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [a7062490-6] 500 Server Error for HTTP GET "/getMyApiDetails/getDetails" com.gateway.config.AuthorizationException: Invalid at com.gateway.filters.CustomAuthorizationFilter.lambda$apply$0(CustomAuthorizationFilter.java:60) ~[classes/:na] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): Original Stack Trace: at com.gateway.filters.CustomAuthorizationFilter.lambda$apply$0(CustomAuthorizationFilter.java:60) ~[classes/:na] at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125) ~[reactor-core-3.4.19.jar:3.4.19] at reactor.core.publisher.MonoPublishOn$PublishOnSubscriber.run(MonoPublishOn.java:181) ~[reactor-core-3.4.19.jar:3.4.19]
错误原因分析
doOnError不改变响应流:doOnError是副作用操作,仅在错误发生时执行逻辑,但不会替换原有的错误流,因此错误仍会向上抛出,导致网关返回500而非预期的401。- 重复调用
chain.filter(exchange):doOnNext中再次调用chain.filter,会导致授权通过时下游API被调用两次,同时授权失败时,原错误流未被正确处理,响应仍会走下游逻辑返回200。 - 错误处理方法未正确整合到响应流:
onError方法返回的Mono<Void>没有被作为错误处理的返回值,而是在doOnError中异步执行,无法终止原有响应流。 getPermissions中不必要的调度器设置:WebClient本身基于Reactor实现非阻塞调用,subscribeOn(Schedulers.immediate())和publishOn(Schedulers.immediate())无意义,反而可能干扰流的执行。
修正后的代码
过滤器代码
@Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); // 防护空指针:先判断Authorization头是否存在 List<String> authHeaders = request.getHeaders().get(HttpHeaders.AUTHORIZATION); if (authHeaders == null || authHeaders.isEmpty()) { return onError(exchange, HttpStatus.UNAUTHORIZED); } String authorizationHeader = authHeaders.get(0); return getPermissions(authorizationHeader) .flatMap(pu -> { if (!isAuthorized(pu)) { return onError(exchange, HttpStatus.UNAUTHORIZED); } else { return chain.filter(exchange); } }) // 捕获调用授权API时的所有异常,统一返回401 .onErrorResume(ex -> onError(exchange, HttpStatus.UNAUTHORIZED)); }; }
获取权限的API调用(移除多余调度器+处理API错误响应)
private Mono<PermissionResponse> getPermissions(String authorizationHeader) { return webClientBuilder.build() .get() .uri(authPermissionsUrl) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .header(HttpHeaders.AUTHORIZATION, authorizationHeader) .retrieve() // 处理授权API返回的错误状态码,转为自定义异常 .onStatus(HttpStatus::isError, clientResponse -> Mono.error(new AuthorizationException("Authorization API request failed"))) .bodyToMono(PermissionResponse.class); }
错误响应处理(可选:添加响应体)
private Mono<Void> onError(ServerWebExchange exchange, HttpStatus httpStatus) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(httpStatus); // 可选:返回JSON格式的错误信息 response.getHeaders().setContentType(MediaType.APPLICATION_JSON); String errorBody = "{\"code\":\"401\",\"message\":\"Unauthorized\"}"; DataBuffer buffer = response.bufferFactory().wrap(errorBody.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)); }
关键说明
- 用
onErrorResume替代doOnError:onErrorResume可以捕获错误并返回新的Mono,终止原有错误流,确保返回自定义的401响应。 - 移除重复的
chain.filter调用:仅在授权通过时返回chain.filter(exchange),保证下游API只被调用一次。 - 完善空指针防护:提前检查Authorization头是否存在,避免
get(0)抛出空指针异常。 - 处理授权API的错误响应:通过
onStatus捕获授权API返回的错误状态码,统一转为自定义异常,确保所有授权相关异常都能触发401响应。
内容的提问来源于stack exchange,提问作者Deepak P R
相关产品推荐
相关产品推荐

