1. 程式人生 > >老被跨域問題煩?看看都有哪些處理方法

老被跨域問題煩?看看都有哪些處理方法

前面寫的《IT技術人員的自我修養》,沒想到幾天內收到了不少良好的反饋,在此感謝大家的關注。往後會不定時分享一些技術、管理領域的工作經驗總結與感悟,歡迎大家持續關注、交流。最近被問及一個跨域的問題,包括之前面試時發現很多面試者對跨域及其處理也是一知半解,故本文對該問題進行了梳理總結,以供參考。

1. 什麼是跨域

理解什麼是跨域,就要先了解一個叫“同源策略”的東西,什麼是“同源策略”?這是瀏覽器為了網站訪問安全,對來自不同源的請求做一些必要的訪問限制的一種策略。那什麼叫“同源”呢?我們知道,一個http請求地址一般包含四部分:協議://域名:埠/路徑

,所謂同源,就是前面三者,即協議、域名、埠都一樣。舉例說明,假如我們有一個地址 http://blog.jboost.cn/docker-1.html, 來看以下地址是否與它同源

地址是否同源說明
https://blog.jboost.cn/docker-1.html 不同源 協議不同,一個http,一個https
http://www.jboost.cn/docker-1.html 不同源 域名不同
http://blog.jboost.cn:8080/docker-1.html 不同源 埠不同,一個是預設埠80,一個是8080
http://blog.jboost.cn/docker-2.html 同源 雖然路徑不同,但協議、域名、埠(預設80)都相同

 

那麼瀏覽器對不同源的請求做了哪些訪問限制呢?共有三種限制

  1. 對Cookie、LocalStorage,以及IndexDB(瀏覽器提供的類NoSQL的一個本地資料庫)的訪問

  2. 對DOM的訪問

  3. AJAX請求

而跨域就是要打破這種訪問限制,對不同源的資源請求也能順利進行,最常見的就是AJAX請求,比如前後端分離架構中,兩者服務域名不同,前端通過AJAX直接訪問服務端介面,就會存在跨域問題。

2. 為什麼會存在跨域

前面說“同源策略”時已經提到,瀏覽器是為了網站的訪問安全,才設定了跨域這道屏障。那麼前面所說的三種限制,分別都是如何來保障網站安全的。

  1. 對本地儲存Cookie、LocalStorage、IndexDB的訪問限制
    我們系統的登入憑證一般是通過在Cookie中設定 SESSIONID(如針對瀏覽器表單請求)或直接返回 token(如針對REST請求)的形式返回給客戶端的,比如Tomcat是通過在Cookie中設定名為 JSESSIONID 的屬性來儲存的,而一般REST請求的token前端會儲存於 LocalStorage 中,如果不存在訪問限制,則你訪問的其它網站可能就會獲取到這些憑證,然後偽造你的身份來發起非法請求,這就太不安全了。

  2. 對DOM的訪問限制
    如果不對DOM進行訪問限制,那麼其它網站,尤其一些釣魚網站,就可以通過 <iframe> 的形式拿到你訪問網站的DOM,進而獲取到你輸入的一些敏感資訊,比如使用者名稱、密碼…

  3. 對AJAX請求的限制
    同源策略規定,AJAX請求只能發給同源的網址,否則就會報錯。至於為什麼要限制,一方面是避免1中所提到偽造非法請求,另一方面我理解是AJAX過於靈活,如果不做限制,可能網站的介面資源就會被其它網站隨意使用,就像你的私有物品被別人招呼都不打任意拿去用一樣。 


總之,同源策略是瀏覽器提供的最基本的一種安全保障機制或約定。

3. 怎麼實現跨域訪問

我們平常遇到的跨域問題基本都出現在AJAX請求的場景,一般而言,可以通過代理、CORS、JSONP等方式來解決跨域問題。

3.1 代理

既然“同源策略”是瀏覽器端的機制,那我們就可以繞開瀏覽器,最常見的做法就是使用代理,如 Nginx,比如我們前端專案的域名是 http://blog.jboost.cn,服務端介面域名是 http://api.jboost.cn,我們在 Nginx 中提供如下配置

server{
    # 埠
    listen 80;
    # 域名
    server_name blog.jboost.cn;
    # 所有 http://blog.jboost.cn/api/xxx 請求都會被轉發到 http://api.jboost.cn/api/xxx
    location ^~ /api {
        proxy_pass http://api.jboost.cn;
    }
}

 

則前端通過AJAX請求服務端介面 http://api.jboost.cn/api/xxx 都可以改為通過 http://blog.jboost.cn/api/xxx 來訪問,從而避免不同源的跨域問題。 

3.2 CORS

CORS是Cross-Origin Resource Sharing的簡寫,即跨域資源共享,CORS需要服務端與瀏覽器同時支援,目前所有瀏覽器(除IE10以下)都支援CORS,因此,實現CORS,主要就是服務端的工作了。例如在Spring Boot中,我們可通過如下配置註冊一個CorsFilter的過濾器來實現跨域支援。

 1 @Configuration
 2 @ConditionalOnClass({Servlet.class, CorsFilter.class})
 3 public class CORSAutoConfiguration {
 4 
 5     @Bean
 6 @ConditionalOnMissingBean(name = "corsFilterRegistrationBean")
 7 public FilterRegistrationBean corsFilterRegistrationBean() {
 8         UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
 9 
10         CorsConfiguration corsConfiguration = new CorsConfiguration();
11         corsConfiguration.applyPermitDefaultValues();
12         corsConfiguration.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
13         corsConfiguration.addExposedHeader(HttpHeaders.DATE);
14 
15         corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
16 
17         CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
18         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
19         filterRegistrationBean.setFilter(corsFilter);
20         filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
21         filterRegistrationBean.addUrlPatterns("/*");
22 
23         return filterRegistrationBean;
24     }
25 }

 

其實質就是在響應訊息的Header中新增幾個屬性,主要有 

  • Access-Control-Allow-Origin 必需,表示允許跨域的請求源,可以是具體的域名,也可以是 * ,表示任意域名

  • Access-Control-Allow-Methods 必需,表示允許跨域訪問的HTTP方法,如GET、POST、PUT、DELETE等,可以是 * ,表示所有

  • Access-Control-Allow-Headers 如果請求包括 Access-Control-Request-Headers 頭資訊,則必需,表示伺服器支援的所有頭資訊欄位

3.3 JSONP

JSONP是利用瀏覽器對HTML一些標籤(如 <script><img>等)的 src 屬性不具有同源策略限制的特性實現的,如前端新增

<script type="text/javascript" src="http://api.jboost.cn/hello?name=jboost&callback=jsonpCallback"/>

並且定義JS方法 jsonpCallback。服務端介面返回內容需要是JS方法jsonpCallback的呼叫格式,如jsonpCallback({"name":"jboost"}),這樣在jsonpCallback方法中就可以獲取服務端實際返回的結果資料{"name":"jboost"}了。
JSONP方式的侷限性也很明顯,一是隻支援GET請求——你沒見過哪些<script><img>標籤是POST請求吧,二是需要對服務端返回資料格式做處理。

4. 總結

三種跨域支援的實現,代理方式最簡單,對客戶端、服務端都不具有侵入性,但如果需要支援的請求源比較多,或者是與第三方對接的話,代理方式就不太適用了。CORS相對來說是一種標準的處理方式,並且通過過濾器的方式對業務程式碼也沒有任何侵入性。而JSONP方式侷限性較大,只支援GET,並且需要服務端做返回資料格式的支援。可針對具體情況選擇適用的方式。