我的踩坑之旅-跨域问题引发bug

Posted dhh-blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的踩坑之旅-跨域问题引发bug相关的知识,希望对你有一定的参考价值。

场景:

        由于业务原因需要在请求中添加一个信息表明请求的source,经过一轮方案的评审,大家共同决定把这source信息存放在消息header中。前端小伙伴听完之后心里暗自偷笑:就一行的代码的事,请求的时候在消息头中添加source:xxxx,这么轻松。然后也没有测试就直接发布了。刚发布没多久,用户纷纷过来投诉各种页面打不开。

cause:

       用户刚投诉,小伙伴们纷纷跑到后台抓取日志查看,但是啥也没有。只能亲测了页面了,一打开页面立马在console上看到各种红点,全显示各种跨域问题。当时就纳闷了,项目中有考虑跨域问题呀,后台配置了CORSFilter,以前一直也没有什么问题呀。经过各种确认之后才发现是由于前端加了自定义消息头的原因。

跨域流程具体分析:

      说到跨域,首先有已个关键词:同源性。同源性指的是当前页面的协议号,域名,端口号与所请求的资源的协议号,域名,端口号保持一致。浏览器为什么要这么做呢?这是从安全方面考虑,防止XSS攻击。但是现实场景中,很多情况又不得不跨域请求。这该怎么办呢?W3C增加了CORS标准,这个标准允许浏览器向跨源服务器发出XMLHttpRequest请求。CORS需要浏览器和服务器同时支持。在跨域请求中可以分为简单请求和非简单请求,简单请求指的是——请求方法为GET,POST,HEAD,请求响应头只能为:Accept,Accept-Language,Last-Event-ID,Content-type,且Content-type的值只能为application/x-www-form-urlencoded、multipart/form-data、text/plain。其它的请求都属于非简单请求。

      简单请求:简单请求相对于非跨域请求在请求header中添加了一个origin头,表明本次请求的来源,服务器收到请求,根据origin头判断是否支持本次请求,如果支持返回响应结果,并在响应中添加了一个Access-Control-Allow-Origin头,如果服务器不支持本次请求,会返回一个正常的http响应,正常的响应指的是http status code正常,但是没有响应内容。

      非简单请求:非简单请求相对于非跨域请求增加了一次请求,这次请求俗称“预检”请求,这个请求的request method 是OPTIONS方法,header头中有origin和Access-Control-Request-Header,origin头的意义跟上文的一致,Access-Control-Request-Header头包含的信息是本次请求(真实请求,不是指嗅探请求)的所有请求头。这个请求顾名思义就是嗅探下服务器是否支持本次请求。嗅探的内容包括:服务器是否支持真实请求的request method,是否支持真实请求的消息头(具体是从Access-Control-Request-Header取出值,然后跟服务器配置的可接受消息头进行比对),查看当前网页所在的域名是否在服务器的许可名单中(查看origin头中的值是不是在服务器的允许域名列表中),若服务器不支持本次真实请求的话,嗅探请求会返回403,真实请求也不会发生了。

    服务器端的配置:以上讨论的是浏览器端做的操作,当然那些操作用户无法感知,程序猿也不需要特别开发,一切都是浏览器自主完成,目前大部分浏览器都支持CORS。CORS需要浏览器和服务器共同配合完成,那服务器端需要配置什么呢?总结起来就那么几点:哪些域名是服务器认可得,哪些请求方法是服务器认可的,哪些请求头是服务认可的,是否允许设置cookie。下面利用spingMVC的CORSFilter来配置服务器(不止这一种方案,还有其他诸如在nginx配置响应头)作个示例,具体如下:

<filter>
        <filter-name>CORS</filter-name>
        <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
        <init-param>
            <param-name>cors.allowOrigin</param-name>
            <param-value>*</param-value>
        </init-param>
        <init-param>
//配置支持的方法
            <param-name>cors.supportedMethods</param-name>
            <param-value>GET,POST, HEAD, PUT, DELETE</param-value>
        </init-param>
        <init-param>
//配置支持的消息头
            <param-name>cors.supportedHeaders</param-name>
            <param-value>Accept,Origin, Authorization, X-Requested-With, Content-Type, Last-Modified</param-value>
        </init-param>
        <init-param>
//配置响应结果的暴露的消息头
            <param-name>cors.exposedHeaders</param-name>
            <param-value>Set-Cookie</param-value>
        </init-param>
        <init-param>
//是否允许cookie
            <param-name>cors.supportsCredentials</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CORS</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

服务器端配置之后,就可以进行“预检”请求了,“预检“”请求之后,你会发现响应消息头中相对于非跨域请求会增加几个消息头:Access-Contol-Allow-Origin(表明服务器允许的消息来源域),Access-Control-Allow-Method(表明服务器允许的请求方法),Access-Control-Allow-Header(表明服务器允许的请求头),Access-Control-Allow-Credentials(是否允许Cookie,若不允许,前端就拿不到cookie了),浏览器收到这几个头之后,觉得真实请求满足这些条件,接下来就会发起真实的请求了。

总结:回到最开始的bug问题,这个问题是因为前端开发人员在请求中自定义了一个消息头source,不在简单请求的范围内,属于非简单请求,在发起“预检”请求时,由于后台CORSFilter中没有配置允许该请求头,倒致“预检”请求403。

以上是关于我的踩坑之旅-跨域问题引发bug的主要内容,如果未能解决你的问题,请参考以下文章

踩坑adb——我的一次使用adb命令的踩坑之旅

快应用开发的踩坑之旅

美团热修复Robust的踩坑之旅-使用篇

mac下使用snailSVN的踩坑之旅

初入gulp的踩坑之旅

vue中集成blockly的踩坑之旅