HTTP的同源策略与跨域资源共享(CORS)机制
字数 3259 2025-08-18 11:38:08

HTTP同源策略与跨域资源共享(CORS)机制详解

一、同源策略(Same-Origin Policy)

1.1 同源策略定义

同源策略是浏览器实施的一种重要安全机制,它限制了一个源(origin)的文档或脚本如何与另一个源的资源进行交互。

1.2 同源判定标准

两个URI被认为是同源当且仅当它们的以下三个部分完全相同:

  • 协议(protocol)
  • 主机(host)
  • 端口(port)

例如:

  • http://example.com/page1http://example.com/page2 是同源
  • http://example.comhttps://example.com 不同源(协议不同)
  • http://example.comhttp://sub.example.com 不同源(主机不同)
  • http://example.comhttp://example.com:8080 不同源(端口不同)

1.3 受同源策略限制的操作

浏览器在以下操作时会检查同源策略:

  1. 以跨站点方式调用XMLHttpRequest或Fetch API
  2. Web字体(用于CSS中@font-face的跨域字体使用)
  3. WebGL textures
  4. 使用drawImage绘制到canvas的图像/视频帧
  5. 样式表(用于CSSOM访问)

1.4 同源策略的两种表现

  1. 限制发起AJAX请求:阻止跨域的XMLHttpRequest或Fetch请求
  2. 拦截跨站请求的返回结果:允许请求发送但阻止JavaScript获取响应

二、跨域资源共享(CORS)

2.1 CORS概述

跨域资源共享(Cross-Origin Resource Sharing)是一种解决跨域请求的方案,通过使用额外的HTTP头来告诉浏览器允许跨域请求。

2.2 CORS工作机制

  1. 浏览器自动在跨域请求中添加Origin
  2. 服务器通过Access-Control-Allow-Origin等响应头决定是否允许该跨域请求
  3. 对于非简单请求,浏览器会先发送预检请求(OPTIONS)

三、简单请求与非简单请求

3.1 简单请求条件

请求必须同时满足以下所有条件才被视为简单请求:

HTTP方法限制

  • GET
  • HEAD
  • POST

Content-Type限制

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

HTTP头限制
只能包含以下安全的首部字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

其他限制

  • 请求中的XMLHttpRequestUpload对象没有注册任何事件监听器
  • 请求中没有使用ReadableStream对象

3.2 简单请求的处理流程

  1. 浏览器直接发送请求,不发送预检请求
  2. 服务器响应中包含Access-Control-Allow-Origin
  3. 浏览器检查响应头,决定是否让JavaScript获取响应

3.3 非简单请求条件

满足以下任一条件即为非简单请求:

HTTP方法

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

HTTP头

  • 设置了CORS安全首部字段集合之外的其他首部字段

Content-Type

  • 不是application/x-www-form-urlencoded、multipart/form-data或text/plain

其他

  • XMLHttpRequestUpload对象注册了事件监听器
  • 使用了ReadableStream对象

3.4 非简单请求的处理流程

  1. 浏览器先发送OPTIONS预检请求
  2. 服务器响应预检请求,包含CORS相关头
  3. 浏览器检查预检响应,决定是否发送实际请求
  4. 如果允许,发送实际请求
  5. 服务器响应实际请求
  6. 浏览器检查响应头,决定是否让JavaScript获取响应

四、CORS相关HTTP头

4.1 请求头

  1. Origin: 表示请求的来源

    • 示例: Origin: http://example.com
  2. Access-Control-Request-Method: 用于预检请求,表示实际请求将使用的方法

    • 示例: Access-Control-Request-Method: POST
  3. Access-Control-Request-Headers: 用于预检请求,表示实际请求将携带的自定义头

    • 示例: Access-Control-Request-Headers: X-Custom-Header

4.2 响应头

  1. Access-Control-Allow-Origin: 指定允许访问资源的源

    • 示例: Access-Control-Allow-Origin: http://example.com
    • 特殊值: * (表示允许任何源,但不能与credentials一起使用)
  2. Access-Control-Allow-Credentials: 是否允许浏览器发送凭据(cookie等)

    • 示例: Access-Control-Allow-Credentials: true
  3. Access-Control-Allow-Methods: 用于预检响应,表示允许的HTTP方法

    • 示例: Access-Control-Allow-Methods: POST, GET, OPTIONS
  4. Access-Control-Allow-Headers: 用于预检响应,表示允许的自定义头

    • 示例: Access-Control-Allow-Headers: X-Custom-Header
  5. Access-Control-Expose-Headers: 允许JavaScript访问的额外响应头

    • 示例: Access-Control-Expose-Headers: X-Custom-Header
  6. Access-Control-Max-Age: 预检请求的缓存时间(秒)

    • 示例: Access-Control-Max-Age: 86400

五、CORS实现示例

5.1 简单请求示例

前端代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>AJAX</title>
</head>
<script>
function submitRequest() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://127.0.0.1:8888/get", true);
    xhr.withCredentials = true;  // 携带凭据
    xhr.send();
    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4 && xhr.status === 200){
            alert(xhr.responseText)
        }
    }
}
</script>
<button onclick="submitRequest()">AJAX</button>
</html>

后端代码(Flask):

@app.route('/get', methods=['GET'])
def get():
    if session.get('user','')=='admin':
        ret = "Admin do something!"
    else:
        ret = "No Privilege..."
    
    resp = make_response(ret)
    resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
    resp.headers['Access-Control-Allow-Credentials'] = 'true'
    resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH"
    return resp

5.2 非简单请求示例

前端代码:

<html>
<title>{{ Evil }}</title>
<center><h1> Reset Password </h1>
<head>
<script type="text/javascript">
function submitRequest() {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://127.0.0.1:8888/json", true);
    xhr.setRequestHeader("Accept", "application/json");
    xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
    xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    xhr.withCredentials = true;
    xhr.send(JSON.stringify({"action":"change passwd"}));
    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4 && xhr.status === 200){
            alert(xhr.responseText)
        }
    }
}
</script>
</head>
<body>
<button onclick="submitRequest()">Conform</button>
</body>
</html>

后端代码(Flask):

@app.route('/json', methods=['GET','POST','OPTIONS'])
def json():
    if request.method == 'GET':
        return render_template('json.html', Evil="Benign")
    else:
        if session.get('user','')=='admin':
            data=request.json
            ret='Admin do '+data["action"]
        else:
            ret="No Privilege2..."
        
        resp=make_response(jsonify({'result': ret}))
        resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
        resp.headers['Access-Control-Allow-Credentials'] = 'true'
        resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH"
        resp.headers['Access-Control-Allow-Headers'] = "origin, content-type, accept, x-requested-with"
        return resp

六、安全注意事项

  1. Credentials与通配符:当使用Access-Control-Allow-Credentials: true时,Access-Control-Allow-Origin不能为*,必须指定明确的源。

  2. CSRF防护:虽然CORS提供了一定保护,但仍需防范CSRF攻击,特别是对于简单请求(GET、POST等)。

  3. 敏感信息暴露:谨慎设置Access-Control-Expose-Headers,避免暴露敏感头信息。

  4. 预检请求缓存:合理设置Access-Control-Max-Age可以优化性能,但过长可能带来安全风险。

七、常见问题

  1. 为什么我的请求被拦截了?

    • 检查是否为跨域请求
    • 检查服务器是否正确设置了CORS头
    • 检查请求是否为非简单请求但没有正确处理预检请求
  2. 为什么设置了Access-Control-Allow-Origin: *还是无法获取响应?

    • 如果请求需要携带凭据(cookie等),则不能使用*,必须指定明确的源
  3. 如何调试CORS问题?

    • 使用浏览器开发者工具查看网络请求
    • 检查请求是否包含Origin
    • 检查响应是否包含正确的CORS头
  4. CORS与JSONP的区别?

    • CORS是现代浏览器支持的官方标准
    • JSONP是旧式跨域解决方案,只支持GET请求
    • JSONP存在安全隐患(XSS风险)
HTTP同源策略与跨域资源共享(CORS)机制详解 一、同源策略(Same-Origin Policy) 1.1 同源策略定义 同源策略是浏览器实施的一种重要安全机制,它限制了一个源(origin)的文档或脚本如何与另一个源的资源进行交互。 1.2 同源判定标准 两个URI被认为是同源当且仅当它们的以下三个部分完全相同: 协议(protocol) 主机(host) 端口(port) 例如: http://example.com/page1 和 http://example.com/page2 是同源 http://example.com 和 https://example.com 不同源(协议不同) http://example.com 和 http://sub.example.com 不同源(主机不同) http://example.com 和 http://example.com:8080 不同源(端口不同) 1.3 受同源策略限制的操作 浏览器在以下操作时会检查同源策略: 以跨站点方式调用XMLHttpRequest或Fetch API Web字体(用于CSS中@font-face的跨域字体使用) WebGL textures 使用drawImage绘制到canvas的图像/视频帧 样式表(用于CSSOM访问) 1.4 同源策略的两种表现 限制发起AJAX请求 :阻止跨域的XMLHttpRequest或Fetch请求 拦截跨站请求的返回结果 :允许请求发送但阻止JavaScript获取响应 二、跨域资源共享(CORS) 2.1 CORS概述 跨域资源共享(Cross-Origin Resource Sharing)是一种解决跨域请求的方案,通过使用额外的HTTP头来告诉浏览器允许跨域请求。 2.2 CORS工作机制 浏览器自动在跨域请求中添加 Origin 头 服务器通过 Access-Control-Allow-Origin 等响应头决定是否允许该跨域请求 对于非简单请求,浏览器会先发送预检请求(OPTIONS) 三、简单请求与非简单请求 3.1 简单请求条件 请求必须同时满足以下所有条件才被视为简单请求: HTTP方法限制 : GET HEAD POST Content-Type限制 : text/plain multipart/form-data application/x-www-form-urlencoded HTTP头限制 : 只能包含以下安全的首部字段: Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width 其他限制 : 请求中的XMLHttpRequestUpload对象没有注册任何事件监听器 请求中没有使用ReadableStream对象 3.2 简单请求的处理流程 浏览器直接发送请求,不发送预检请求 服务器响应中包含 Access-Control-Allow-Origin 头 浏览器检查响应头,决定是否让JavaScript获取响应 3.3 非简单请求条件 满足以下任一条件即为非简单请求: HTTP方法 : PUT DELETE CONNECT OPTIONS TRACE PATCH HTTP头 : 设置了CORS安全首部字段集合之外的其他首部字段 Content-Type : 不是application/x-www-form-urlencoded、multipart/form-data或text/plain 其他 : XMLHttpRequestUpload对象注册了事件监听器 使用了ReadableStream对象 3.4 非简单请求的处理流程 浏览器先发送OPTIONS预检请求 服务器响应预检请求,包含CORS相关头 浏览器检查预检响应,决定是否发送实际请求 如果允许,发送实际请求 服务器响应实际请求 浏览器检查响应头,决定是否让JavaScript获取响应 四、CORS相关HTTP头 4.1 请求头 Origin : 表示请求的来源 示例: Origin: http://example.com Access-Control-Request-Method : 用于预检请求,表示实际请求将使用的方法 示例: Access-Control-Request-Method: POST Access-Control-Request-Headers : 用于预检请求,表示实际请求将携带的自定义头 示例: Access-Control-Request-Headers: X-Custom-Header 4.2 响应头 Access-Control-Allow-Origin : 指定允许访问资源的源 示例: Access-Control-Allow-Origin: http://example.com 特殊值: * (表示允许任何源,但不能与credentials一起使用) Access-Control-Allow-Credentials : 是否允许浏览器发送凭据(cookie等) 示例: Access-Control-Allow-Credentials: true Access-Control-Allow-Methods : 用于预检响应,表示允许的HTTP方法 示例: Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers : 用于预检响应,表示允许的自定义头 示例: Access-Control-Allow-Headers: X-Custom-Header Access-Control-Expose-Headers : 允许JavaScript访问的额外响应头 示例: Access-Control-Expose-Headers: X-Custom-Header Access-Control-Max-Age : 预检请求的缓存时间(秒) 示例: Access-Control-Max-Age: 86400 五、CORS实现示例 5.1 简单请求示例 前端代码 : 后端代码(Flask) : 5.2 非简单请求示例 前端代码 : 后端代码(Flask) : 六、安全注意事项 Credentials与通配符 :当使用 Access-Control-Allow-Credentials: true 时, Access-Control-Allow-Origin 不能为 * ,必须指定明确的源。 CSRF防护 :虽然CORS提供了一定保护,但仍需防范CSRF攻击,特别是对于简单请求(GET、POST等)。 敏感信息暴露 :谨慎设置 Access-Control-Expose-Headers ,避免暴露敏感头信息。 预检请求缓存 :合理设置 Access-Control-Max-Age 可以优化性能,但过长可能带来安全风险。 七、常见问题 为什么我的请求被拦截了? 检查是否为跨域请求 检查服务器是否正确设置了CORS头 检查请求是否为非简单请求但没有正确处理预检请求 为什么设置了 Access-Control-Allow-Origin: * 还是无法获取响应? 如果请求需要携带凭据(cookie等),则不能使用 * ,必须指定明确的源 如何调试CORS问题? 使用浏览器开发者工具查看网络请求 检查请求是否包含 Origin 头 检查响应是否包含正确的CORS头 CORS与JSONP的区别? CORS是现代浏览器支持的官方标准 JSONP是旧式跨域解决方案,只支持GET请求 JSONP存在安全隐患(XSS风险)