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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma)外,还允许哪些 header 可以被访问到。
  • Access-Control-Max-Age — 表明了预检请求的结果被缓存的时长。
  • Access-Control-Allow-Credentials — 如果对预检请求的响应中这个Header被设置为True,那么在实际请求中,允许携带用户凭证。
  • Access-Control-Allow-Methods — 预检请求响应 header 的一部分,表明了在实际请求中那些请求方法是合法的。
  • Access-Control-Allow-Headers — 预检请求响应 header 的一部分,表明了在实际请求中哪些请求header是合法的。

处理流程

请求分类

  1. If the following conditions are true, follow the simple cross-origin request algorithm:
  2. 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 。

效果图

cors simple request

预检请求

我们使用 发送 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请求会触发预检请求。

效果图

cors preflight request

文中代码可在 ThomasXu18/cors_study: An Exemple for CORS. 获得。


参考链接