作為一隻前端菜鳥,跨域方面只懂得JSONP和CORS,並未曾深入了解。但隨著春招越來越近,就算是菜鳥也要猛振翅膀。最近幾日仔細研究了跨域問題,寫下這篇文章,希望對開發者們有幫助。在閱讀本文之前,希望您對以下知識略有了解。
因為瀏覽器的同源策略規定某網域下的客戶端在沒明確授權的情況下,不能讀寫另一個網域的資源。而在實際開發中,前後端常常是相互分離的,並且前後端的專案部署也常常不在一個伺服器內或在一個伺服器的不同連接埠下。前端想要取得後端的數據,就必須發起請求,如果不錯一些處理,就會受到瀏覽器同源策略的約束。後端可以收到請求並返回數據,但是前端無法收到數據。
回應頭 | 解釋 |
#Access-Control-Allow-Origin
| Access-Control- Allow-Origin頭中攜帶了伺服器端驗證後的允許的跨域請求域名,可以是一個具體的域名或是一個*(表示任意域名)。 |
Access-Control-Expose-Headers
| #Access-Control-Expose-Headers頭用於允許傳回給跨網域請求的回應頭列表,在列表中的回應頭的內容,才可以被瀏覽器存取。 |
Access-Control-Max-Age
| #Access-Control-Max-Age用於告知瀏覽器可以將預先檢查請求傳回結果快取的時間,在快取有效期內,瀏覽器會使用快取的預先檢查結果判斷是否發送跨域請求。 |
Access-Control-Allow-Methods
| Access-Control-Allow-Methods用於告知瀏覽器可以在實際發送跨域請求時,可以支援的請求方法,可以是特定的方法清單或是一個*(表示任意方法)。 |
如何使用
SpringBoot 設定CORS範例
一個spring boot專案中關於CORS配置的一段程式碼
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String temp = request.getHeader("Origin");
httpServletResponse.setHeader("Access-Control-Allow-Origin", temp);
// 允许的访问方法
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
// Access-Control-Max-Age 用于 CORS 相关配置的缓存
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept,token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
JSONP 跨域
jsonp的原理就是使用HTML中的<script>標籤可以跨域引入資源。所以動態建立一個<srcipt>標籤,src為目的介面 get封包 處理資料的函式名稱。後台收到GET請求後解析並回傳函數名稱(資料)給前端,前端<script>標籤動態執行處理函數<br/>觀察下面程式碼</script>
前端程式碼
nbsp;html>
<meta>
<title>Title</title>
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为getData
script.src = 'http://localhost:8080/users?username=xbc&callback=handleData';
document.body.appendChild(script);
// 回调执行函数
function handleData(res) {
data = JSON.stringify(res)
console.log(data);
}
</script>
後端程式碼(nodejs)
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
var data = {
user: 'xbc',
password: '123456'
}
res.write(fn + '(' + JSON.stringify(data) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
在該範例中,前台收到的res是這樣的
前端頁面是這樣的
注意
#JSONP既是利用了,那麼就只能支援GET請求。其他請求無法實作
nginx 反向代理實作跨域
想法
既然瀏覽器有同源策略限制,那我們把前端項目和前端要請求的api介面位址放在同源下不就可以了?再結合web伺服器提供的反向代理,便可以在前端和後端都不做設定的情況下解決跨域問題。
以nginx為例
後端真實後台位址:http://xxx.xxx.xxx.xxx:8085
後台位址使用tomcat部署的spring boot項目名為gsms_test
nginx伺服器位址: http://xxx.xxx.xxx.xxx:8082
tomcat和nginx都用docker架設的,做了連接埠轉送
使用條件:開發環境為linux系統
nginx /etc/nginx/conf.d/default.conf
設定程式碼如下
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
# root /usr/share/nginx/html/dist; # 前端项目路径
# index index.html index.htm;
proxy_pass http://localhost:8001/; # 前端本机地址,实现自动更新
autoindex on;
autoindex_exact_size on;
autoindex_localtime on;
}
location /gsms_test/ {
proxy_pass 后端真实地址;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
不同域下頁面通訊而跨域
window.name iframe 跨域
window.name是瀏覽器中一個視窗所共享的數據,在不同的頁面(甚至不同網域)加載後依舊存在(如果沒修改則值不會變化),並且可以支援非常長的
name 值(2MB)。比如
a域的某頁面想取得b域某頁的數據,可以在b域修改window.name值,a域切換到b域再切回來即可得到b域的window.name值。可是我們在開發上肯定不想頁面切來切去,所以就要結合iframe來實現。
範例(以thinkjs實作)
a 域程式碼如下
nbsp;html>
<meta>
<title>A 域</title>
<h1>server A</h1>
<script>
function getData() {
var iframe = document.getElementById('proxy');
iframe.onload = function () {
var name = iframe.contentWindow.name; // 获取iframe窗口里的window.name值
console.log(name)
}
// 由于iframe信息传递也受同源策略限制,所以在window.name被B域修改后,将iframe转回A域下。以便获取iframe的window.name值
iframe.src = 'http://127.0.0.1:8360/sub.html'
}
</script>
<iframe> </iframe>
b 域代碼
nbsp;html>
<meta>
<title>New ThinkJS Application</title>
<h1>server 2</h1>
<script>
window.name = 'user: xbc';
</script>
注意
由於受同源策略限制,父頁面取得跨域的iframe頁面的資訊不全,所以要在iframe的window.name被B域修改後,轉為A域下的任一頁面(該一面不得修改window.name),在進行取得。
代理頁面 iframe 實作跨域存取
由於iframe與父頁面相互存取也受同源策略限制,所以要藉助一代理頁面實作跨域。
個人認為有些麻煩,若有興趣請看前端如何用代理頁面解決iframe跨域存取的問題?
總結
以上幾種皆是本人用過或測試過的跨域方法,還有postMessage,WebSocket等跨域方法由於從未接觸不做說明。在專案中具體使用那些方法還需具體考慮各種問題
情況 |
方法 |
#只有GET請求 |
JSONP |
對相容性及瀏覽器版本無要求 |
CORS |
對相容性及瀏覽器版本有要求 |
iframe 或伺服器反向代理(linux 環境下開發) |
#