CORS(Cross-origin resource sharing) 跨域资源共享
定义
MDN 给出的定义是:
CORS (Cross-Origin Resource Sharing) is a system, consisting of transmitting HTTP headers, that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.
The same-origin security policy forbids cross-origin access to resources. But CORS gives web servers the ability to say they want to opt into allowing cross-origin access to their resources.
相关 Headers
Request Header
Origin
— 表明了跨域请求或者预检请求的来源。Access-Control-Request-Headers
— 出现在预检请求中,表明实际跨域请求中会使用哪些Header
。Access-Control-Request-Method
— 出现在预检请求中,表明实际跨域请求所使用的Method
。
Response Header
Access-Control-Allow-Origin
— 表明哪些Origin
被允许跨域共享资源,*
表示允许任何Origin
。Access-Control-Expose-Headers
— 表明跨域请求中,除基本header(Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
)外,还允许哪些header
可以被访问到。Access-Control-Max-Age
— 表明了预检请求的结果被缓存的时长。Access-Control-Allow-Credentials
— 如果对预检请求的响应中这个Header
被设置为True
,那么在实际请求中,允许携带用户凭证。Access-Control-Allow-Methods
— 预检请求响应header
的一部分,表明了在实际请求中那些请求方法是合法的。Access-Control-Allow-Headers
— 预检请求响应header
的一部分,表明了在实际请求中哪些请求header是合法的。
处理流程
请求分类
- If the following conditions are true, follow the simple cross-origin request algorithm:
- The request method is a simple method and the force preflight flag is unset.
- Each of the author request headers is a simple header or author request headers is empty.
- Otherwise, follow the cross-origin request with preflight algorithm.
满足以下条件,就可视为 简单请求,否则则需要先发起一次预检请求:
- 使用下列方法之一:
- GET
- HEAD
- POST
- 请求Header仅包括下列几个
- Accept
- Accept-Language
- Content-Language
- Content-Type (仅限 text/plain multipart/form-data application/x-www-form-urlencoded)
简单请求
我们在本地模拟一下跨域,python3 内置的 http server 会 监听本地 0.0.0.0:8000 ,我们再用 node 运行一个 server 监听 localhost:8080 来模拟跨域
- index.html
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div style="width: 300px; background: #b3b3b3">
<p> Open the DevTools and then click the Network tab. </p>
<p> Observe the network request activity. </p>
</div>
<div style="width: 300px; background: #e3e3e3">
<ul>
<li>Simple Request
<ul>
<li><button onclick="simple_success()">Success</button></li>
<li><button onclick="simple_failed()">Failed</button></li>
</ul>
</li>
</ul>
</div>
<script type="text/javascript">
function simple_success() {
$.ajax({
type: 'GET',
url: 'http://localhost:8080/simple-s',
success: function(data){alert(data.msg);},
failure: function(errMsg) { alert(errMsg); }
});
}
function simple_failed() {
$.ajax({
type: 'GET',
url: 'http://localhost:8080/simple-f',
success: function(data){alert(data.msg);},
failure: function(errMsg) { alert(errMsg); }
});
}
</script>
</body>
</html>
- server.js
/**
* simple server for CORS test.
**/
var express = require('express')
var cors = require('cors')
var app = express()
app.get('/simple-s', cors(), function(req, res, next) {
res.json({msg: 'This is CORS-allowed.'})
})
app.get('/simple-f', function(req, res, next) {
res.json({msg: 'This is CORS-disallowed.'})
})
app.listen(8080, function() {
console.log('CORS-enabled web server listening on port 8080')
})
分别运行起来之后 我们点击 index.html 中的 两个按钮可以发现一个是成功的跨域请求,而另一个会被浏览器阻止。可以在开发者工具中观察这两个请求的 Request Header 和 Response Header 。
效果图
预检请求
我们使用 发送 json 数据的post请求来触发预检请求。
在 index.html 中添加两个按钮
<li>Preflight Request
<ul>
<li><button onclick="preflight_success()">Success</button></li>
<li><button onclick="preflight_failed()">Failed</button></li>
</ul>
</li>
<script type="text/javascript">
function preflight_success() {
$.ajax({
type: 'POST',
url: 'http://localhost:8080/pre-s',
data: '[{"test": "test"}]',
contentType: "application/json", // Using application/json will generate a preflight request
dataType: 'json',
success: function(data){alert(data.msg);},
failure: function(errMsg) { alert(errMsg); }
});
}
function preflight_failed() {
$.ajax({
type: 'POST',
url: 'http://localhost:8080/pre-f',
data: '[{"test": "test"}]',
contentType: "application/json", // Using application/json will generate a preflight request
dataType: 'json',
success: function(data){alert(data.msg);},
failure: function(errMsg) { alert(errMsg); }
});
}
</script>
在 server.js 中添加两个接口,其中一个允许预检请求和跨域,另一个不允许跨域。
app.options('/pre-s', cors()) // enable preflight request for this request
app.post('/pre-s', cors(), function(req, res, next) {
res.json({msg: 'This post request is CORS-allowed.'})
})
app.post('/pre-f', function(req, res, next) {
res.json({msg: 'This post request is CORS-disallowed.'})
})
两个端口运行起来之后,我们点击两个POST的按钮,会发现一个成功,另一个会被浏览器阻止。由于我们的 Content-Type 取值为 application/json 所以post请求会触发预检请求。
效果图
文中代码可在 ThomasXu18/cors_study: An Exemple for CORS. 获得。