CORS(Cross-Origin Resource Sharing)
跨源资源共享(CORS)是W3C制定的一种浏览器安全机制,允许网页从不同的源(Origin)请求资源。在同源策略下,浏览器禁止网页向不同源的服务器发送请求或读取响应。CORS通过在HTTP响应头中添加特定的字段来告知浏览器"允许哪些源访问这些资源"。CORS是现代Web开发中不可或缺的机制,因为前后端分离架构(SPA + REST API)和微服务架构都需要跨域通信。CORS的实现分为两种:简单请求和预检请求。简单请求满足以下条件:方法为GET、HEAD或POST,且请求头不超出简单头列表(Accept、Accept-Language、Content-Language、Content-Type为application/x-www-form-urlencoded、multipart/form-data或text/plain,且不超过1024字节)。不满足简单请求条件的请求都会触发预检。CORS在服务器端实现,浏览器负责执行。常见的CORS配置位置包括:Web服务器配置文件(Nginx、Apache)、应用框架中间件(Express cors中间件、Django CORS中间件)、API网关(AWS API Gateway、Kong)。
预检请求(Preflight Request)
预检请求是浏览器在发送实际的跨域请求之前,自动发送的一个OPTIONS请求。其目的是询问服务器是否允许该跨域请求,以及允许哪些方法和头。预检请求的触发条件包括:使用PUT、DELETE、PATCH等非简单方法;请求头中包含自定义头(如Authorization、X-Custom-Header);Content-Type为application/json等非简单类型;请求中包含凭证(credentials: include)。预检请求的工作流程如下:浏览器先发送一个OPTIONS请求,请求头包含Origin、Access-Control-Request-Method(实际请求的方法)、Access-Control-Request-Headers(实际请求的自定义头列表)。服务器返回响应,包含Access-Control-Allow-Origin(允许的源)、Access-Control-Allow-Methods(允许的方法列表)、Access-Control-Allow-Headers(允许的头列表)等。浏览器检查响应,如果所有检查通过,再发送实际的请求;否则,浏览器阻止实际请求并在控制台报告CORS错误。预检请求会增加一次网络往返(round-trip),对性能有一定影响,可以通过Access-Control-Max-Age头缓存预检结果来优化。
Origin(源)
源(Origin)是Web安全中的核心概念,由协议(scheme)、主机名(host)和端口号(port)三部分组成。例如,https://example.com:443是一个源,http://localhost:3000是另一个源。以下情况视为不同源:协议不同(http vs https)、主机名不同(example.com vs api.example.com)、端口号不同(:80 vs :3000)。即使两个页面都在example.com下,如果协议不同(http vs https)或端口不同,也视为跨域。Origin的特殊值null(小写字符串)用于以下场景:来自沙箱iframe的请求、来自data: URI的请求、来自本地文件(file://)的请求、用户手动修改Origin后的请求。服务器在处理CORS时,需要将请求中的Origin头与配置的允许列表进行匹配。Origin匹配是精确匹配,https://example.com不匹配https://example.com:443(虽然443是HTTPS的默认端口)。W3C规范要求Origin匹配必须考虑协议、主机和端口的完整比较。
Access-Control-Allow-Origin
Access-Control-Allow-Origin是CORS响应中最重要的头部,用于告知浏览器哪些源可以访问资源。该头有两种格式:精确的Origin值(如https://example.com)和通配符*(表示允许所有源)。当服务器收到预检或实际请求时,会检查请求头中的Origin。如果Origin在允许列表中,服务器在响应中返回匹配的Origin值(而非*)。如果Allow-Origin为*,表示允许任何源访问。注意:当Access-Control-Allow-Credentials为true时,Allow-Origin不能为*,必须返回具体的Origin值。Access-Control-Allow-Origin不支持逗号分隔的多个值,如果需要支持多个Origin,服务器需要动态判断请求的Origin并返回匹配的值(或不返回Origin头以拒绝请求)。Access-Control-Allow-Origin的值在浏览器端是大小写敏感的。一些服务器框架(如Node.js Express)使用中间件自动处理Origin匹配,开发者只需配置允许的Origin列表即可。
Access-Control-Allow-Methods
Access-Control-Allow-Methods是预检响应中的头部,用于告知浏览器服务器允许哪些HTTP方法。该头的值是逗号分隔的HTTP方法列表,例如:GET, POST, PUT, DELETE, OPTIONS, HEAD。浏览器会将此列表与实际请求的方法进行比较,如果实际方法不在列表中,浏览器会阻止请求并报告CORS错误。Allow-Methods通常需要包含服务器实际支持的所有方法,包括OPTIONS(某些框架自动处理OPTIONS请求时可能不需要显式列出)。在预检请求中,浏览器发送Access-Control-Request-Method头来声明实际要使用的方法,服务器需要在Allow-Methods中包含该方法。Allow-Methods的值不区分大小写,但按照HTTP规范,方法名应该使用大写。一些服务器框架在配置CORS时允许开发者指定允许的方法列表,框架会自动设置Allow-Methods头。对于简单请求(GET、HEAD、POST),浏览器不会发送预检请求,因此Allow-Methods对简单请求没有影响。
Access-Control-Allow-Headers
Access-Control-Allow-Headers是预检响应中的头部,用于告知浏览器服务器允许哪些请求头。该头的值是逗号分隔的头名称列表,例如:Content-Type, Authorization, X-Requested-With, X-Custom-Header。浏览器会将此列表与实际请求的自定义头进行比较,如果实际请求中包含不在列表中的自定义头,浏览器会阻止请求并报告CORS错误。Allow-Headers的匹配不区分大小写(HTTP头名称不区分大小写)。对于简单请求(GET、HEAD、POST),浏览器只允许使用简单头(Accept、Accept-Language、Content-Language、Content-Type),不需要服务器在Allow-Headers中声明。但对于非简单头(如Authorization、X-Custom-Header),服务器必须在Allow-Headers中声明才能通过预检。一些服务器框架提供了自动配置Allow-Headers的功能,可以根据实际使用的请求头动态设置。Access-Control-Allow-Headers对预检请求有效,对后续的实际请求也有效(即使实际请求不再发送OPTIONS)。
Access-Control-Allow-Credentials
Access-Control-Allow-Credentials是CORS响应中的头部,用于告知浏览器是否允许在跨域请求中携带凭证(cookies、HTTP认证信息、客户端证书等)。该头的值为true或false(不区分大小写)。当为true时,浏览器会在跨域请求中自动附带cookies和其他凭证信息,并允许JavaScript读取响应。当为false或不存在时,浏览器不会在跨域请求中发送凭证。重要限制:当Allow-Credentials为true时,Access-Control-Allow-Origin不能为*(通配符),必须返回具体的Origin值。这是因为*表示允许所有源,如果同时允许凭证,任何网站都可以通过伪造请求获取用户的凭证数据。使用Allow-Credentials时,服务器还需要在Access-Control-Expose-Headers中声明前端需要读取的自定义响应头。Access-Control-Allow-Credentials对预检响应和实际响应都有效。在前端,只有当请求配置中包含credentials: 'include'时,浏览器才会在请求中发送凭证。
Access-Control-Max-Age
Access-Control-Max-Age是预检响应中的头部,用于告知浏览器预检结果可以缓存多长时间(以秒为单位)。在缓存有效期内,浏览器不会重复发送预检请求,直接使用缓存的结果。这对于减少预检请求的网络开销非常重要,特别是对于频繁请求的API端点。Max-Age的值是一个非负整数,表示缓存的秒数。例如,Access-Control-Max-Age: 86400表示预检结果可以缓存24小时。不同浏览器对Max-Age的最大值有不同的限制:Firefox最大支持86400秒(24小时),Chrome最大支持7200秒(2小时),Safari最大支持5秒(某些版本)。当Max-Age为0时,表示不缓存预检结果,每次跨域请求前都会发送预检。当Max-Age不存在时,浏览器使用默认缓存策略(通常较短)。建议设置合理的Max-Age值(如3600秒=1小时),在减少预检开销和保持配置灵活性之间取得平衡。注意:浏览器在清除缓存或网络变更时会重新发送预检请求,不受Max-Age限制。
Access-Control-Expose-Headers
Access-Control-Expose-Headers是实际响应中的头部,用于告知浏览器哪些响应头可以被JavaScript代码读取。默认情况下,浏览器只允许JavaScript读取"简单"响应头(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)。对于自定义响应头(如X-RateLimit-Remaining、X-Total-Count、ETag等),即使服务器返回了,JavaScript也无法通过getResponseHeader()或getAllResponseHeaders()读取。服务器需要在Access-Control-Expose-Headers中声明这些头,JavaScript才能读取。例如:Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-Total-Count。Expose-Headers对预检请求和实际响应都有效,但通常只需要在实际响应中设置。Access-Control-Expose-Headers不区分大小写,多个头名称用逗号分隔。如果不设置Expose-Headers,浏览器仍然可以读取CORS安全头(如Access-Control-Allow-Origin)和简单响应头。一些API框架(如Spring Boot)提供了自动配置Expose-Headers的功能。
UD5工具箱