You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]

错误原因分析

  1. doOnError不改变响应流:doOnError是副作用操作,仅在错误发生时执行逻辑,但不会替换原有的错误流,因此错误仍会向上抛出,导致网关返回500而非预期的401。
  2. 重复调用chain.filter(exchange):doOnNext中再次调用chain.filter,会导致授权通过时下游API被调用两次,同时授权失败时,原错误流未被正确处理,响应仍会走下游逻辑返回200。
  3. 错误处理方法未正确整合到响应流:onError方法返回的Mono<Void>没有被作为错误处理的返回值,而是在doOnError中异步执行,无法终止原有响应流。
  4. 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));
}

关键说明

  1. 用onErrorResume替代doOnError:onErrorResume可以捕获错误并返回新的Mono,终止原有错误流,确保返回自定义的401响应。
  2. 移除重复的chain.filter调用:仅在授权通过时返回chain.filter(exchange),保证下游API只被调用一次。
  3. 完善空指针防护:提前检查Authorization头是否存在,避免get(0)抛出空指针异常。
  4. 处理授权API的错误响应:通过onStatus捕获授权API返回的错误状态码,统一转为自定义异常,确保所有授权相关异常都能触发401响应。

内容的提问来源于stack exchange,提问作者Deepak P R

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 02:24:30