小伙子把我坑了,改造网关filter,一直报跨域

13,792 阅读7分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

闲聊


1、项目研发负责人需要具备主动性

阿里招聘简历上,常常会带上一句话:具备owner意识。它的逻辑是怎样的呢?

【执行者角度】:我完成某项任务,I do it~

【owner角度】入手任务前,这个任务为了解决什么东西(核心价值)?它会引发你的思考,就是我有没有其他更好的方案去解决这个问题,其次是在实施前思考可行性、风险、扩展点。任务执行中,项目进度是不是在预期内,然后遇到什么问题需要主动寻求帮助。任务完成后,我们做这个项目它有没有发挥应有的价值,这时就要观察项目运转情况、业务情况,如果它是一个持续的过程,那么我们还要规划下一步内容。

我觉得这是我在当项目负载负责人的时候学习到的,我可以清晰观察到很多项目半死不活,就它只是服务于当下,可能上线后没有多少人去用它,它的价值完全没有发挥出来,just 一个摆设!

坚持做正确的事 --马斯克

马斯克在成功之前,大家都觉得他很疯狂,什么回收火箭,面对一次次失败,网上键盘侠噼里啪啦吐槽更猛;当他成功的时候,大家就会觉得很了不起。是啊,很多人只看花绽放的结果,但是花在成长过程,大家就会抗拒新的事物、新的尝试,并且加以诋毁。所以坚持做正确的事~

前言


我们最近项目终于开始接入统一登录了,其实sso统一登录项目也耽搁了很长一段时间,然后登录验证逻辑是交给权限中心去搞,也就是需要将之前网关登录鉴权filter改写,通过sdk的方式去注入到网关中。然后到了测试阶段,前端反馈有些接口一直报跨域,我就奇怪了,之前接口一直是好好的,然后网关也有配置跨域设置,咋会这样?

image.png

排查历程


1. 第一步:我观察网关跨域配置是否有问题

image.png

没有问题,就是从网上copy下来的,哈哈哈

2. 思考:为什么会跨域?

小伙子提供了请求traceid给我,让我apm看看什么问题,好家伙,这网关给你改造然后你让我来看啥问题?

image.png

我们看到是能正常返回的,但是!是异常,验证已失效,请重新登录。

3. 第二步,检查网关跨域配置

为什么其他服务异常不会跨域,偏偏这登录鉴权异常就跨域?其实浏览器的跨域是因为同源机制导致,就是response需要加上特定header,Access-Control-Allow-Origin,Access-Control-Allow-Credentials;

那么我就去查网关是否有对异常进行处理:

@Slf4j
public class XxxFilter extends AbstractErrorWebExceptionHandler implements Ordered {


    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), request -> renderErrorJsonResponse(request, errorAttributes));
    }

    private Mono<ServerResponse> renderErrorJsonResponse(ServerRequest request, ErrorAttributes errorAttributes) {

        Map<String, Object> errorProperties = getErrorAttributes(request, ErrorAttributeOptions
                .of(ErrorAttributeOptions.Include.STACK_TRACE));

        // 获取合适的处理器对 response body 进行重写
        return rewriteFunctions.stream()
                .orElseGet(() -> {
                    Throwable throwable = errorAttributes.getError(request);

                    return ServerResponse.ok()
                            .contentType(MediaType.APPLICATION_JSON)
                            .body(BodyInserters.fromValue(exceptionAdvice.handleException(throwable)));
                });
    }

}

网关地方会重写异常的返回response,我idea本地测试一波,发现如果是服务异常,它不会走这里的,也就是只有出现Throwable异常抛出的时候才会被捕获。报错的接口却是走这个异常,问题慢慢揭开面纱~

image.png

4. 第三步:查看接口的归属以及实现

我就去瞄了一眼,这个接口是哪个服务的,这是谁的部下,这么勇猛。好家伙,就是这期接入统一登录的接口,而且是白名单!

接下来我们就要进去看看对应的实现了,逻辑里头没有判断token是否过期啊,怎么肥事,老六。我就找啊找啊,发现居然还自带一个filter来检查访问的token是否有效的

image.png

这个就是gaetway web-flux的过滤器写法对吧,到这里还没有问题,我们再往下看

image.png 当我看到方法里头的实现的时候,我就觉得坏事了,因为我们网关filter是不会这么写的,直接抛出异常。

why?

业务系统我们习惯是throw new xxException对吧,这个没有错,因为业务系统有统一异常捕获。小伙子改造网关filter验证,也认为网关有异常捕获对吧。

其实呢,网关已经是最外层了,理论上不应该再抛出异常,应该直接返回响应的异常码还有信息。

到这里,我们就能解释清楚第三步,为啥这个接口走的是网关异常捕获,而不是直接返回了。 因为你在filter这里抛出异常,然后被网关异常handler捕获,重写了response

注意这里重写了response,导致header头的跨域头丢失了。

5. 第四步:在网关异常强行加上跨域头

注意这里是强行加上跨域头,你想想网关怎么可以有抛出异常呢?它只能有异常响应,不能直接抛出异常,它是两回事,前者是封装异常码、异常信息,后者是直接抛出异常,他们区别就是500,系统异常,那是万不得已才去全局捕获的,业务系统还能理解,但是网关的话人家捕获异常是为了防止万一,你偏偏要去通过抛出异常来处理,去走最后兜底方案。

image.png

到这里我们就解决了跨域问题

为什么直接改成输出异常码就不会有问题?


比如说跨域filter在这个鉴权filter后面,不就跨域头没了对吧,所以我们需要探索他们之间的先后顺序

首先CorsWebFilter 它是继承webfilter,正儿八经的网关拦截器,然后我们再看下鉴权filter,它也是继承webfilter,这下好玩了,我们只能通过order方法来决定他们的先后顺序。

其次还有另外做法,实现globalfilter,它虽然带上filter字样,其实是属于webhandler里头一个拦截器责任链。我们通过以前这篇文章介绍到网关构造,会先走webfilter,然后走webhandler,这个顺序不需要设定,一开始会走添加跨域头corsfilter,然后再进去webhandler,这样的好处也有一个,越往前它的粒度是越大的,我们这个鉴权的拦截器是针对网关某些白名单接口去限制的,范围会更小,应该滞后去处理。

这样的设计网关才符合漏斗形过滤机制,一层一层筛选以及处理!

image.png

回顾


导致的原因其实就是因为接入统一登录的时候,有些权限接口是白名单,通过网关filter来鉴权,但是呢,里头通过抛出异常的方式来实现校验token,导致走了网关兜底异常handler,通过改写response响应,导致跨域头没有塞回去。

我的观点: 1、网关filter不应该跟业务系统一样,直接抛出异常,而是直接返回异常码+异常信息+响应头。因为这里只是丢失了跨域头,那么其他什么灰度header标识,或者跟前端约定的东西都会丢失。我认为网关统一异常处理是作为最后兜底的方案去解决,不是作为业务异常来搞的

image.png

下课觉得对你有帮助的,点点赞,关注下博主呗,感谢