一次跨域問題引發的思考

問題

今天在聯調接口的時候,發現了一個奇怪的錯誤Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight responsejavascript

剛開始覺得只是一次簡單的跨域訪問,後來在網上查閱資料,發現這是「預檢請求」
這裏發送的是一次 OPTIONS請求,得到瀏覽器支持的請求類型,那麼什麼時候會進行預檢請求呢?

只有在瀏覽器發送非簡單請求的狀況下,纔會發送預檢請求html

那麼什麼樣叫作非簡單請求,什麼又是簡單請求呢?前端

只要同時知足如下兩大條件,就屬於簡單請求。java

  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

凡是不一樣時知足上面兩個條件,就屬於非簡單請求。node

這是該問題所引出的相關名詞,本着好奇的原則,繼續往下走...linux

跨域

跨域是針對瀏覽器端而產生的,服務器端的通訊並不會產生這樣的問題,這就引出了第一個解決跨域的思路----代理服務器(瀏覽器請求本地起的服務器—同源服務器,再由後者請求外部服務)而且跨域並不是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。ios

代理服務器

咱們利用node能夠實現代理服務器,筆者是利用了http-proxy-middlewaregit

var proxy = require('http-proxy-middleware');

var apiProxy = proxy('/api', {target: 'http://www.example.org', changeOrigin: true});
複製代碼

在這裏changeOrigin: true引發了個人興趣,思考爲何會存在該項配置?這裏提到了虛擬主機與獨立主機的概念github

在咱們ping baidu.com的時候會獲得IP地址,瀏覽器經過這個IP地址是能夠訪問網站的(獨立主機),在咱們ping apple.com的時候獲得的IP地址是不能經過瀏覽器的方式訪問的(共享主機或者虛擬主機) 虛擬主機是一種在單一主機或主機羣上,實現多網域服務的方法,能夠運行多個網站或服務的技術,所以它能夠共享服務器的內存、CPU等資源web

虛擬主機主要有如下幾種類型

  1. 網址名稱對應(Name-based)
  2. IP地址對應(IP-based)
  3. Port端口號對應

http-proxy-middleware中使用的changeOrigin配置項正式因爲使用了網址名稱對應的虛擬主機。

跨域資源共享(CORS)

在本篇博客中,該種方式會進行詳細介紹,利用AJAX進行跨域資源共享的時候,咱們就會引出文章剛開始所遇到的問題,簡單請求和非簡單請求。

  • 簡單請求: 在這裏爲了模仿跨域的請求利用到express框架,先創建兩個文件,一個後端接口文件backend.js。
'use strict';
var express = require('express'),
	app = express();

app.get('/auth/:id/', function (req, res) { 
	res.send({ id:req.params.id }); 
});

app.listen(3000);
複製代碼

而後利用backend.js指向一個html文件,在html裏面發送ajax請求,模仿跨域(端口號不一樣)

'use strict';

var express = require('express'),
	app = express();

app.use(express.static("./"))
app.get('/', function (req, res) {
	res.render(__dirname + '/index.html');
});

app.listen(4000);
複製代碼

最後是html文件,引入了axios

<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.get('http://localhost:3000/auth/1')
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
複製代碼

當發起請求時,咱們發現

即便在返回結果是200的狀況下,仍然時跨域提醒,這就與前面的觀點是一致的 跨域並不是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。 服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含 Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤。 那麼解決方法也很簡單,在後端加上容許跨域請求。

app.all('*', function(req, res, next) { 
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Content-Type", "application/json;charset=utf-8");
	next(); 
});
複製代碼

這樣就能夠正常訪問

  • 非簡單請求 依舊利用上面的代碼,在發送請求POST時,當頭部Content-Typeapplication/json時,代碼以下:
<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.post('http://localhost:3000/auth', {
		name: 'gao'
	}, {
		headers: {
			'Content-Type': 'application/json'
		}
	})
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
複製代碼

會發送如圖所示的預檢請求:

當頭部 Content-Typeapplication/x-www-form-urlencoded時,代碼以下:

<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.post('http://localhost:3000/auth', {
		name: 'gao'
	}, {
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded'
		}
	})
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
複製代碼

會發現,瀏覽器不會發送預檢請求而且返回成功

那麼如何保證在發送預檢請求時,瀏覽器不會報錯呢?

後端代碼以下:

'use strict';
var express = require('express'),
	app = express();

app.all('*', function(req, res, next) { 
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Access-Control-Allow-Headers", "content-type");
	res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 
	res.header("Content-Type", "application/json;charset=utf-8");
	next(); 
});

app.get('/auth/:id/', function (req, res) { 
	res.send({ id:req.params.id }); 
});

app.post('/auth', function (req, res) { 
	res.send({ id:req.params.id }); 
});


app.listen(3000);
複製代碼

會發現加入了這兩行代碼便可。

res.header("Access-Control-Allow-Headers", "content-type"); // 這裏的content-type是默認的,根據客戶端請求來設定
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 
複製代碼

此時POST會發送兩次請求:

當預檢請求經過後,再次發送 POST請求。

其餘

像其餘的技術,包括JSONP、LocalStorage、cookie、postMessage在此很少作介紹,能夠參考阮大的博客進行參考。

奇淫技巧

在有些時候,後端的接口因爲各類不能知足咱們所須要的跨域支持,那麼就須要前端本身下功夫,咱們思考一下,既然瀏覽器因爲安全策略阻止了返回的結果,那麼就能夠在瀏覽器的安全策略上進行修改。

設置Chrome瀏覽器的 disable-web-security, 實現跨域訪問後端的接口。

windows

"C:\Users\UserName\AppData\Local\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir

mac

open -a "Google Chrome" --args --disable-web-security --user-data-dir

linux

chromium-browser --disable-web-security

參考文章

相關文章
相關標籤/搜索