又是跨域,這次搞定它!
本文轉載自微信公眾號「小姐姐味道」,作者小姐姐養(yǎng)的狗02號。轉載本文請聯(lián)系小姐姐味道公眾號。
世界上沒有沒碰到跨域的程序員。
在平常工作中,我們不止一次聽到過跨域這個詞,尤其是和前端打交道的時候;當然安全部門有時候也會刷存在感,推給你 XSS這樣看起來高大上的名詞;再就是從http協(xié)議升級成https協(xié)議的時候,也會碰到。
在后端開發(fā)中,并沒有跨域這么麻煩的東西。因為只要網(wǎng)絡連的通,我們就能夠構造請求。所謂的跨域,是對于瀏覽器來說的,限制了各種條條框框,以便能夠安全的馴服javaScript這只野獸。
換句話說,脫離了主流的瀏覽器,并沒有跨域這么一說。就像是楚河漢界,脫離了棋盤,就是個談資而已。
所以來吧,看看瀏覽器為了達到自己的目的,都干了些啥。
1. 跨域和同源
所謂的同源,指的是兩個URL的協(xié)議(protocal)、域名(host)、端口(port)都相同的情況,可以說是非常嚴格了。下面給出一個示例。
對于http://xjjdog.cn/index.html來說,下面展示了4種不同的情況。
【1】https://xjjdog.cn/index.html 因為協(xié)議不同,一個是http一個是https,所以它們是不同源的。
【2】https://xjjdog.cn:8080/index.html 因為端口不同,一個是80,一個是8080,所以它們是不同源的。
【3】http://mall.xjjdog.cn/index.html 因為主機不同,所以依然是不同源的
【4】http://xjjdog.cn/index.htm 這種情況,因為只有目錄是不同的,所以是同源的。
那么在不同源(跨域)的情況下,js的執(zhí)行都有哪些限制呢?
首先是存儲資源不共享。比如Cookie、LocalStorage 和 IndexDB(瀏覽器數(shù)據(jù)庫)等,都不能相互讀取。
其次是跨域的情況下,DOM和Javascript對象都無法獲取。
更要命的是,Ajax請求無法發(fā)送,限制了前端程序員的發(fā)揮。
但隨著業(yè)務的增長和域名的增加,跨域的需求是越來越多,瀏覽器的默認行為,成為了這個功能的攔路虎。所以我們需要尋找有效的方法,來突破這條加載自己脖子上的鎖套。
2. CORS 跨域資源共享
前端工程師很聰明,發(fā)明了各種各樣的請求方法。常見的有:
- jsonp 使用javascript的代理模式,動態(tài)的創(chuàng)建script標簽。比如常見的百度統(tǒng)計代碼,雖然不同源,但是你仍然能把信息發(fā)送過去。jsonp只能支持GET請求,不支持POST請求。
- document.domain + iframe 這個是利用iframe加載主域名相同的資源。
- location.hash + iframe 依然是利用iframe等,使用的是全局對象,用起來很繞。
- postMessage Html5的新功能,專門用來解決跨域。我們只要發(fā)送端擁有某個窗口的有效js的句柄,就可以通過這套機制向該窗口發(fā)送任意長度的文本信息。但編程的時候,容易忘掉origin的判斷,造成安全問題。
這些方法都需要寫很多代碼,還容易出錯,調試起來也麻煩,所以現(xiàn)在用的最多的,是CORS。
這項技術是W3C的標準,前端代碼幾乎不需要做任何改動,瀏覽器可以自動完成。聽起來非常的魔幻,但它其實是在HTTP協(xié)議上做文章的,要在Http的頭里面,加入一些附加信息。只要服務器支持,就實現(xiàn)了跨域操作。所以通信的關鍵就有前端轉移到了服務器的配置上。 目前,幾乎所有的瀏覽器都支持。
拿Nginx來說,要解決跨域,就得加一些配置。
- location / {
- if ($request_method = 'OPTIONS') {
- add_header 'Access-Control-Allow-Origin' '*';
- add_header 'Access-Control-Allow-Credentials' true;
- add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
- #
- # Custom headers and headers various browsers *should* be OK with but aren't
- #
- add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
- #
- # Tell client that this pre-flight info is valid for 20 days
- #
- add_header 'Access-Control-Max-Age' 1728000;
- add_header 'Content-Type' 'text/plain; charset=utf-8';
- add_header 'Content-Length' 0;
- return 204;
- }
- if ($request_method = 'POST') {
- add_header 'Access-Control-Allow-Origin' '*';
- add_header 'Access-Control-Allow-Credentials' true;
- add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
- add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
- add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
- }
- if ($request_method = 'GET') {
- add_header 'Access-Control-Allow-Origin' '*';
- add_header 'Access-Control-Allow-Credentials' true;
- add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
- add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
- add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
- }
- }
因為跨域,針對于正常的瀏覽器限制來說,相當于開了一條特許通道,所以它的配置非常的細膩。其中最重要的,就是Access-Control-Allow-Origin,我們一般設置成*一了百了,但它可以指定具體的請求來源,也更加安全。所以,在dev環(huán)境調試時,為了方便開發(fā),可以設置成*,而線上最好設置成具體的domain。
Access-Control-Request-Method指定了跨域請求所允許的HTTP方法,我們這里是GET、POST、OPTIONS等。
Access-Control-Allow-Headers,表明服務器支持的所有頭信息字段,用在預檢請求中。值得注意的是,一些簡單的頭部信息,比如Content-Language、Content-Type等,不需要特別聲明。如果你想偷懶,當然也有更好的方法。
- Access-Control-Allow-Headers: *
那么,http的交互,是如何執(zhí)行的呢?
我們假定瀏覽器訪問的網(wǎng)址是http://xjjdog.cn,當瀏覽器訪問的時候,它發(fā)起了一個指向http://xdddog.cn的Ajax請求。正常情況下,這是不能通過的。于是瀏覽器在請求頭中,自動添加了一行。
- Origin: http://xjjdog.cn
xdddog.cn的服務器(nginx)看到這個請求,一對比,可以啊兄弟,我允許你訪問。
- Access-Control-Allow-Origin: *
- Access-Control-Allow-Credentials:true
那請求就可以正常進行下去,否則會觸發(fā)XHR的onerror。
請求類型分為簡單請求和復雜請求,細究起來一點意思都沒有,建議訪問https://www.test-cors.org/進行實際的測試來觀測。
我們上面介紹的,其實就是簡單請求的過程。
對于復雜請求來說,多了一步使用OPTIONS的預檢操作,流程其實也差不多。
3. 其他地方怎么配置?
由于CORS應用非常廣泛,所以現(xiàn)在所有的服務器端軟件,都提供了對它的支持。對于tomcat來說,配置就簡單的多,配置一個filter就可以了。
- <filter>
- <filter-name>CorsFilter</filter-name>
- <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>CorsFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
對于SpringBoot服務來說,就更加簡單,直接通過注解就能完成。
@CrossOrigin(origins = "*",maxAge = 3600)
當然,它的屬性也有很多。
- origins 所有支持域的集合,也就是Access-Control-Allow-Origin
- allowedHeaders 允許請求頭重的header,不設置的話就是全部支持
- exposedHeaders 響應頭中允許訪問的header
- methods 請求支持的方法
- allowCredentials 是否允許cookie隨請求發(fā)送,使用時必須指定具體的域
- default 預請求的結果的有效期,默認30分鐘
所以,在SpringBoot下實現(xiàn)跨域,就是這么任性的簡單。
4. End
跨域問題,在前后分離的架構下,幾乎100%都會遇到??缬蛟L問的限制,是瀏覽器做的文章,我們可以使用CORS來繞過去。既然是繞,那就不要一股腦的全部設置成*,雖然這樣搞非常的讓人省心。
而有時候你確實會遇到連CORS都處理不了的跨域問題。在這種情況下,最好要求你的客戶,升級一下支持的瀏覽器試試。畢竟有些特立獨行的瀏覽器,是非常IE的。
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個人微信xjjdog0,歡迎添加好友,進一步交流。































