有关CORS跨域访问,这事没完

前后端分离模大势所趋,跨域问题更是老生常谈。

1. 跨域访问的问题背景

浏览器最基本的安全规范: 同源策略 。所谓同源是指域名、协议、端口相同

不同源的浏览器脚本(javascript、fetch方法,ActionScript、canvas)在没有明确授权的情况下,不能读写对方的资源。

CORS就是w3c和浏览器厂商为解决跨域资源共享问题而推出的标准方案: juejin.cn/post/754047...

① 浏览器遇到跨域请求,浏览器会自动携带Origin标头(指示请求来自于哪个站点),判断需要预检preflight后,还会自动给你发起Option请求。

② Web服务器实现跨站访问授权逻辑, 授权结果在Response中以 Access-Control--******* 标头体现

③ 浏览器会遵守Access-Control--*******-- 标头所施加的跨域限制, 即使CORS跨域访问失败, 响应数据也已经到了浏览器, 这个tip可以辅助你fiddler排查问题。

2. 预检option请求

当前端使用脚本请求一个跨域资源时,如果是非简单请求 ,浏览器会自动帮你先发出一个OPTIONS查询请求,称为预检(cors-preflight-request)。

作用是询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用那些HTTP动词和头信息字段。

只有得到肯定答复,浏览器才会发生正式的XHR请求。

2.1 反正都要到服务端,为什么还要多一次option预检请求?

CORS 预检请求是一种 CORS 请求,用于检查 CORS 协议是否被理解以及服务器是否了解使用特定的请求方法和标头。

2.2 预检请求的行为特征

该请求header中会包含以下两个字段:

  • Access-Control-Request-Method: 该字段的值对应当前请求类型,例如 GET、POST、PUT等,浏览器会自动产生该标头。

  • Origin: 跨域时,浏览器会自动帮你带上。

  • 可选 Access-Control-Request-Headers: 对应当前请求会携带的自定义header,多个字段用逗号分隔,浏览器会自动帮你带上, eg:标识请求流水的x-request-id,用于Auth鉴权的Authorization请求头。

对于 OPTIONS 请求,按照规范实现的服务端会响应一组HTTP header,但不会返回任何实体内容。如果服务端支持该跨域请求,建议返回 204状态码(返回200也可以);如果不支持,建议返回403状态码(返回404或其他错误状态码也可以)。

响应的header 对应有以下字段:

  • Access-Control-Allow-Methods: 指示服务器支持哪些CORS方法访问,例如:POST, GET, PUT, DELETE
  • Access-Control-Allow-Headers: 指示服务器允许哪些 自定义header请求头访问
  • Access-Control-Max-Age: 指示浏览器缓存预检请求的响应,也就是缓存Access-Control-Allow-MethodsAccess-Control-Allow-Headers的信息,单位秒。

除此之外, 还会有CORS的常规响应头: Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Expose-Headers

预检请求不会携带凭据,后端应忽略对预检请求的凭据访问

① 预检请求不会带上凭据,故在常见的web编程实践中(凭据认证会在处理管道的前面),预检Options返回非200, 后续的实际请求就不会发起, 所以一定要对Options请求开放凭据访问。

kong网关的CORS的插件,有一个配置Preflight Continue可以帮助规避这个编程实践问题。

默认preflight_continue = false, 意味着插件不将预检请求转发给upstream,所以此处保持默认就好, 网关会根据请求特征和CORS配置产生CORS响应头,并返回你期待的200 OK响应码。

lua 复制代码
function CorsHandler:access(conf)
  local req_origin = kong.request.get_header("Origin")
  if kong.request.get_method() ~= "OPTIONS"
     or not is_origin_provided(req_origin)
     or not kong.request.get_header("Access-Control-Request-Method")
  then
    return
  end

  -- don't add any response header because we are delegating the preflight to
  -- the upstream API (conf.preflight_continue=true), or because we already
  -- added them all
  
  kong.ctx.plugin.skip_response_headers = true

  if conf.preflight_continue then
    return
  end

//......
 return kong.response.exit(HTTP_OK)
end

源码在github.com/Kong/kong/b...

如果你手痒设置成true,那么预检请求就会转发到upstream,需要后端自己去忽略对预检Options请求的认证。

② 预检之后实际的请求,不会再包含Access-Control-Request-** 请求头,只会有Origin请求头了。


我们看到触发预检时,一次AJAX请求会消耗掉两个TTL,严重影响性能。

2.3 节省预检请求,以提升性能

从上文可以看出,有两个方案:

方案1. 发出简单请求 : 不触发预检请求

只要同时满足以下两个条件,就属于简单请求

(1)使用下列方法之一:

  • head
  • get
  • post

(2)请求的Heder是

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type: 只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain

不同时满足上面的两个条件,就属于非简单请求。 很明显,我们常见的Post请求、媒体类型Content-Type=application/json也属于非简单请求,也会触发预检请求。如果不方便改造为简单请求,只有使用方案2了。

方案2. 服务器端设置Access-Control-Max-Age字段

当第一次请求该URL时会发出OPTIONS请求,浏览器会根据返回的Access-Control-Max-Age字段缓存该请求的OPTIONS预检请求的响应结果。

在缓存有效期内,该资源的请求(URL和header字段都相同的情况下)不会再触发预检。

(chrome 打开控制台可以看到,当服务器响应Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉disable cache勾选)

下面是Abp vNext配置CROS的示例:

ini 复制代码
private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
{
     context.Services.AddCors(options =>
     {
         // 无阻塞跨域
        options.AddPolicy(DefaultCorsPolicyName, builder =>
       {
        builder.SetIsOriginAllowed(_ => true)
             .AllowCredentials()
             .AllowAnyHeader()
             .WithMethods(HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete)
                    .SetPreflightMaxAge(TimeSpan.FromHours(24));
        });
     });
}
相关推荐
码兄科技8 小时前
实战:基于Spring Boot + UniApp的地理信息小程序开发
spring boot·后端·uni-app
腾讯云云开发9 小时前
腾讯云 CloudBase 登上 WAIC:我们为 Agent 重新设计了云的生产线
后端
星栈9 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
65岁退休Coder10 小时前
LangChain v1.3.4 笔记 - 05 Agent 上下文记忆
后端
颜酱10 小时前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
JaneConan10 小时前
鸿蒙 韶非 UI 系列:能力调用 startAbilityForResult,跳能力拿回参,鸿蒙能力路由入门
后端·harmonyos
晴空了无痕10 小时前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes
用户83562907805110 小时前
如何使用 Python 在 Excel 中添加、编辑和删除超链接
后端·python
花椒技术10 小时前
原本要 2 天的服务端冒烟前置审查,为什么 3 分半就能出报告?|QA 质量交付实践(三)
后端·ai编程·测试
达达尼昂10 小时前
AI 编程的工程化实践:Flutter AI Harness 的设计与落地
人工智能·后端·全栈