由一個瀏覽器跨域問題引發的思考

1、問題背景:

       公司在做自己的OA系統,採用的前後端分離的開放模式,在與前端對接"獲取當前用戶信息"這個接口時,瀏覽器的控制檯就報了以下錯誤:

Access to XMLHttpRequest at 'http://192.168.0.101:10000/api/login/login?username=1&password=1' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

我們採用token來維護用戶登錄狀態的方案,用戶登錄成功後,以後每次需要登錄的接口都需要在header中帶着這個token,後端用springMVC的攔截器做登錄攔截,一切看着都是這麼的自然,然而......

        第一個對接的是"登錄接口",很順利的通過了。當前端調用第二個接口——"獲取當前用戶信息"接口時,突然前端小姐姐叫了一聲:"哎?這是什麼錯誤?是不是跨域了?"。我當時心想:"不可能呀,我在搭框架的時候已經在後端處理了跨域問題"。下面是我的解決方式:

 @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //表示所有的資源都允許跨域訪問
                .allowedOrigins("*")   //表示所有的源都可以訪問
                 ////表示允許那些http請求方式
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") 
                .allowCredentials(true)  //表示請求中可以攜帶cookie
                .maxAge(3600)    //表示預檢請求的有效期,有效期內不會在發送第二次預檢請求。
                .allowedHeaders("*");  //表示允許所有請求頭
    }

標準的CORS解決方式,也確實能解決跨域問題,但是並不適合我這裏的場景,爲什麼?因爲我使用了攔截器。爲什麼使用了攔截器就不行了?下面聽我分析一下:

2、問題原因:

        爲什麼第一個登錄接口沒事,第二個就有跨域問題?因爲第一個登陸接口我沒做登錄攔截,會執行到上面的方法(此時該登錄請求是一個簡單請求)。第二個接口做了登錄攔截,##但我在請求頭中加入了token值,還是應該可以通過攔截器到達目標方法,也會執行上面的跨域解決方法,爲什麼還是被攔截出現了跨域問題呢?##OK,問題就出現在這裏,就是因爲我在請求頭中加入了token,導致了該請求從一個簡單請求變成了非簡單請求,至於什麼是非簡單請求暫時先不說?但他有一個區別於簡單請求的最大區別:會在正式請求發送之前,先發送一個預檢請求,就是個普通請求,用於檢查目標服務器是否允許當前源跨域訪問。該預檢請求可是什麼參數都不帶的,自然就被我的登錄攔截器給攔下了,然後回去告訴客戶端瀏覽器說目標服務器不允許你跨域訪問,瀏覽器說了聲,哦~,然後就向前端小姐姐拋了一個錯誤,可是把小姐姐難住了~~

3、解決方案:

        既然該預檢請求會被攔截器攔截,導致無法執行我解決跨域的方法,那我只需要在攔截器攔截之前,給請求的響應頭加入允許跨域訪問的響應頭即可。springMVC的本質是servlet,當時學javaWeb時知道filter的執行時機是在servlet之前,那我使用filter來增加響應頭就行了唄。

        自己寫一個filter也行,但其實springMVC早就提供了這麼一個解決跨域的filter——CorsFilter。若是springBoot項目的話,直接在配置類中加入該bean即可。

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");//允許所有的源訪問
        config.setAllowCredentials(true);//允許請求中帶着cookie
        config.addAllowedMethod("*");//允許哪些http請求方式
        config.addAllowedHeader("*");//允許請求頭中額外帶着哪些請求頭
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);//表示所有的資源都允許跨域訪問
        return new CorsFilter(configSource);
    }

其他的解決方式也有很多,這裏我再提供幾種後端解決跨域的方式:

1)自定義filter

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        filterChain.doFilter(servletRequest, servletResponse);
    }

2)重寫springMVC的addCorsMappings方法

@Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //表示所有的資源都允許跨域訪問
                .allowedOrigins("*")   //表示所有的源都可以訪問
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") //表示允許那些http請求方式
                .allowCredentials(true)  //表示請求中可以攜帶cookie
                .maxAge(3600)    //表示預檢請求的有效期,有效期內不會在發送第二次預檢請求。
                .allowedHeaders("*");  //表示允許所有請求頭
    }

3)使用@CrossOrigin註解:更細粒度的跨域訪問控制方式

        注:可用在controller上或方法上,會覆蓋全局的跨域配置

 

@Controller
@RequestMapping("/home")
@CrossOrigin(origins = "*", maxAge = 3600)
public class HomeController {

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

}

 

以上四種方式,根據實際項目中攔截器的使用情況靈活選用,但還是最推薦filter的方式,因爲最強大和通用。

 

4、相關知識

1)簡單請求和非簡單請求

瀏覽器將跨域請求分爲簡單請求和非簡單請求。只要同時滿足以下兩大條件,就屬於簡單請求

(1) 請求方法是以下三種方法之一:
    HEAD
    GET
    POST

(2)HTTP的頭信息不超出以下幾種字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

以上任何一個條件不滿足的,就屬於非簡單請求。非簡單請求就會額外發送"預檢請求"。

2)跨域及產生的原因

     跨域的定義:即通訊協議、ip、端口三個有任何一個不相同就被稱爲跨域。如http://localhost:9000/a.html,要去訪問http://localhost:10000/api/getUser接口,就稱之爲跨域請求,因爲兩者的端口不一樣。此時如果不做處理的話,兩者是無法進行通信的。

    跨域產生的原因:就是瀏覽器的同源策略。該策略是在1995年由網景公司引入的,目前各瀏覽器都支持該策略。該策略的引入也是出於對瀏覽器訪問的一種安全機制,具體例子這裏就不細說了,大家去問度娘吧。

3)標準的解決的方案Cors

      Cors是W3C組織引入的,全稱爲(Cross-origin resource sharing)跨域資源共享,是用來解決瀏覽器跨域訪問問題的一種標準的行業規範,目前各大瀏覽器都支持Cors。

     Cors的核心思想是:通過服務器端增加一系列的允許跨域的響應頭,來解決跨域問題。

5、結束語

      好了,今天就到這裏吧,能把以上部分搞明白,日常開發中的跨域問題基本上都能解決了。

     最後說一下我的心聲:

           在我遇到本文開頭的那個問題之前,我以爲我已經能解決跨域問題了,直到解決那個問題之後,我才發現我所知道的也只是浮於表面的一些東西,更深入的東西還會有,只是我現在還沒碰到而已。所以不要自滿,不要自以爲我最牛逼,別人都是傻逼,每個人都要他所擅長的地方,只是你還沒發現而已。

      你知道的越多,你就知道的越少,這句話至少在計算機領域是很經典的。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章