Access-Control-Allow-Origin 头部如何工作

Access-Control-Allow-Origin 头部如何工作

技术背景

在Web开发中,由于浏览器的同源策略,出于安全考虑,会限制从脚本内部发起的跨源HTTP请求。例如,XMLHttpRequestFetch 都遵循同源策略,一个使用 XMLHttpRequestFetch 的Web应用程序只能向其自身的域名发出HTTP请求。

为了改善Web应用程序,开发者希望浏览器厂商允许跨域请求。于是,跨域资源共享(Cross-Origin Resource Sharing,CORS)机制应运而生,它为Web服务器提供了跨域访问控制,从而实现安全的跨域数据传输。现代浏览器在API容器(如 XMLHttpRequestfetch)中使用CORS来降低跨源HTTP请求的风险。

实现步骤

简单请求

当站点A尝试从站点B获取内容时,站点B可以发送一个 Access-Control-Allow-Origin 响应头,告知浏览器该页面的内容可被某些源访问。源是指域名、协议和端口号的组合。默认情况下,站点B的页面不允许任何其他源访问,使用 Access-Control-Allow-Origin 头部可以为特定的请求源打开跨源访问的大门。

对于站点B希望对站点A可访问的每个资源/页面,站点B应在响应头中提供如下信息:

1
Access-Control-Allow-Origin: http://siteA.com

现代浏览器不会直接阻止跨域请求。如果站点A向站点B请求一个页面,浏览器实际上会在网络层面获取请求的页面,并检查响应头是否将站点A列为允许的请求者域名。如果站点B未表明允许站点A访问该页面,浏览器将触发 XMLHttpRequesterror 事件,并拒绝将响应数据提供给请求的JavaScript代码。

非简单请求

如果请求是非简单请求,浏览器首先会发送一个不带数据的 “预检” OPTIONS请求,以验证服务器是否会接受该请求。满足以下任一条件(或两者都满足)的请求即为非简单请求:

  1. 使用除GET或POST之外的HTTP动词(如PUT、DELETE);
  2. 使用非简单请求头;简单请求头仅包括:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(仅当其值为 application/x-www-form-urlencodedmultipart/form-datatext/plain 时为简单请求头)。

如果服务器以适当的响应头(针对非简单头的 Access-Control-Allow-Headers,针对非简单动词的 Access-Control-Allow-Methods)响应OPTIONS预检请求,且这些响应头与非简单动词和/或非简单头匹配,那么浏览器将发送实际请求。

例如,假设站点A要发送一个PUT请求到 /somePage,且 Content-Type 的值为非简单的 application/json,浏览器会先发送一个预检请求:

1
2
3
4
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

如果服务器成功响应的头信息如下:

1
2
3
Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type

那么浏览器会发送实际请求:

1
2
3
4
5
PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json

{ "myRequestContent": "JSON is so great" }

服务器会像处理简单请求一样,再次发送 Access-Control-Allow-Origin 头:

1
Access-Control-Allow-Origin: http://siteA.com

核心代码

Node.js + Express.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const app = express();

app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});

// 此设置必须在所有路由之前
app.get('/', (req, res) => {
res.send('Hello World!');
});

const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});

PHP

1
2
3
4
<?php
header("Access-Control-Allow-Origin: *");
// 后续代码
?>

.NET Core 3.1 API + Angular

Startup.cs

1
2
3
4
5
6
7
8
9
10
11
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200"));
app.UseHttpsRedirection();
}

Controller

1
2
3
4
5
6
[Authorize]
[EnableCors()]
public class UsersController : ControllerBase
{
// ActionMethods
}

最佳实践

  • 指定具体的源:尽量避免使用通配符 *,因为这会允许任何站点与你的服务器进行交互,存在安全风险。应明确指定允许访问的源,例如 Access-Control-Allow-Origin: http://siteA.com
  • 在生产环境中避免使用浏览器扩展:虽然浏览器扩展(如Firefox的CORS Everywhere和Google Chrome的Allow CORS: Access-Control-Allow-Origin)可以在开发或测试阶段解决CORS问题,但不建议在生产环境中使用,因为它们会绕过浏览器的安全机制。
  • 缓存预检请求:对于非简单请求,服务器可以设置 Access-Control-Max-Age 头,以缓存预检请求的结果,减少不必要的预检请求。

常见问题

CORS头缺失

如果出现 “CORS header ‘Access-Control-Allow-Origin’ missing” 错误,可能是服务器端未正确设置 Access-Control-Allow-Origin 头。需要确保服务器端代码中添加了该头信息。

预检请求失败

如果预检请求失败,可能是服务器未正确响应 Access-Control-Allow-HeadersAccess-Control-Allow-Methods 头。需要检查服务器端代码,确保这些头信息正确设置。

浏览器扩展问题

在使用浏览器扩展解决CORS问题时,可能会导致生产环境中的请求仍然失败。因此,在生产环境中应使用服务器端的CORS配置。


Access-Control-Allow-Origin 头部如何工作
https://119291.xyz/posts/how-access-control-allow-origin-header-works/
作者
ww
发布于
2025年5月29日
许可协议