個人踩坑之旅-跨域問題引起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。其它的請求都屬於非簡單請求。cookie

      簡單請求:簡單請求相對於非跨域請求在請求header中添加了一個origin頭,代表本次請求的來源,服務器收到請求,根據origin頭判斷是否支持本次請求,若是支持返回響應結果,並在響應中添加了一個Access-Control-Allow-Origin頭,若是服務器不支持本次請求,會返回一個正常的http響應,正常的響應指的是http status code正常,可是沒有響應內容。app

      非簡單請求:非簡單請求相對於非跨域請求增長了一次請求,此次請求俗稱「預檢」請求,這個請求的request method 是OPTIONS方法,header頭中有origin和Access-Control-Request-Header,origin頭的意義跟上文的一致,Access-Control-Request-Header頭包含的信息是本次請求(真實請求,不是指嗅探請求)的全部請求頭。這個請求顧名思義就是嗅探下服務器是否支持本次請求。嗅探的內容包括:服務器是否支持真實請求的request method,是否支持真實請求的消息頭(具體是從Access-Control-Request-Header取出值,而後跟服務器配置的可接受消息頭進行比對),查看當前網頁所在的域名是否在服務器的許可名單中(查看origin頭中的值是否是在服務器的容許域名列表中),若服務器不支持本次真實請求的話,嗅探請求會返回403,真實請求也不會發生了。cors

    服務器端的配置:以上討論的是瀏覽器端作的操做,固然那些操做用戶沒法感知,程序猿也不須要特別開發,一切都是瀏覽器自主完成,目前大部分瀏覽器都支持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了),瀏覽器收到這幾個頭以後,以爲真實請求知足這些條件,接下來就會發起真實的請求了。url

總結:回到最開始的bug問題,這個問題是由於前端開發人員在請求中自定義了一個消息頭source,不在簡單請求的範圍內,屬於非簡單請求,在發起「預檢」請求時,因爲後臺CORSFilter中沒有配置容許該請求頭,倒致「預檢」請求403。

相關文章
相關標籤/搜索