跨域問題
瀏覽器的同源策略導致了跨域問題
同源策略限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。
- 同源
如果兩個頁面的協議,端口(如果有指定)和域名都相同,則兩個頁面具有相同的源
下表給出了相對http://store.company.com/dir/page.html
同源檢測的示例:
URL | 結果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 成功 | |
http://store.company.com/dir/inner/another.html | 成功 | |
https://store.company.com/secure.html | 失敗 | 不同協議 ( https 和 http ) |
http://store.company.com:81/dir/etc.html | 失敗 | 不同端口 ( 81 和 80) |
http://news.company.com/dir/other.html | 失敗 | 不同域名 ( news 和 store ) |
跨域問題需要服務端與客戶端相同配合完成,單方面並不能解決問題。
同源策略限制下接口請求的正確打開方式
JSONP
- 使用 nodejs 搭建服務端,監聽 8080 端口
//通過require將http庫包含到程序中
const http = require("http");
//引入url模塊解析url字符串
const url = require("url");
//引入querystring模塊處理query字符串
const querystring = require("querystring");
//創建新的HTTP服務器
const server = http.createServer();
//通過request事件來響應request請求
server
.on("request", function(req, res) {
let urlPath = url.parse(req.url).pathname;
// res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
res.writeHead(200, { "Content-Type": "application/json" });
let data = {
name: "Hello World"
};
if (urlPath === "/jsonp") {
if (req.url.split("?").length >= 2) {
let qs = querystring.parse(req.url.split("?")[1]);
//回調函數名為qs.callback
let callback = qs.callback + "(" + JSON.stringify(data) + ");";
res.end(callback);
} else {
data.name = "noCallbackFunction";
}
}
res.end(JSON.stringify(data));
})
//監聽8080端口
.listen("8080", () => {
//服務器啟動成功
console.log("Server running! listen 8080");
});
這樣,我們需要的服務器搭建好了,在前端請求后加入’/jsonp?callback=functionName’,就將其認定為 JSONP 請求,且 callback 后帶入一個 js 中已有的全局方法。
- 客戶端使用 JS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
function testJSONP(data){
console.log(data);
}
</script>
<script src="http://127.0.0.1:8080/jsonp?callback=testJSONP"></script>
</body>
</html>
在瀏覽器控制台輸出返回結果
使用 jquery 直接跨源訪問,會產生錯誤
<script>
$(function()
{$.get("http://127.0.0.1:8080/jsonp", function(data) {
console.log(data);
})}
);
</script>
產生的錯誤如下:
- 客戶端使用 angularJS,代碼如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<script src="https://cdn.staticfile.org/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
{{name}}
</div>
<script> var app = angular.module("myApp", []); var gitApi = "https://api.github.com?callback=JSON_CALLBACK"; //callback=JSON_CALLBACK固定寫法不能修改 app.controller("myCtrl", function($scope, $http) { $http({ method: "JSONP", url: gitApi }).then( function(response) { //調用成功后執行 console.log("success" + JSON.stringify(response.data)); $scope.name = response.data; }, function(response) { //調用失敗后執行 console.log(response); } ); }); </script>
</body>
</html>
CORS
CORS 是一個 W3C 標准,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出 XMLHttpRequest 請求,從而克服了 AJAX 只能同源使用的限制。
CORS 需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE 瀏覽器不能低於 IE10。
整個 CORS 通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS 通信與同源的 AJAX 通信沒有差別,代碼完全一樣。瀏覽器一旦發現 AJAX 請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現 CORS 通信的關鍵是服務器。只要服務器實現了 CORS 接口,就可以跨源通信。
使用 CORS 設置服務端允許跨域
- Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時 Origin 字段的值,要么是一個*,表示接受任意域名的請求。res.writeHead(200, { "Content-Type": "application/json" ,"Access-Control-Allow-Origin":"*"}); //指定允許跨源訪問的資源 //res.writeHead(200, { "Content-Type": "application/json" ,"Access-Control-Allow-Origin":"http://172.0.0.1"});
- Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否允許發送 Cookie。默認情況下,Cookie 不包括在 CORS 請求之中。設為 true,即表示服務器明確許可,Cookie 可以包含在請求中,一起發給服務器。這個值也只能設為 true,如果服務器不要瀏覽器發送 Cookie,刪除該字段即可。 - Access-Control-Expose-Headers
該字段可選。CORS 請求時,XMLHttpRequest
對象的getResponseHeader()
方法只能拿到 6 個基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必須在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回 FooBar 字段的值。