有关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请求头。

响应的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));
        });
     });
}
相关推荐
码事漫谈1 天前
智能体颠覆教育行业调研报告:英语、编程、语文、数学学科应用分析
后端
蓝-萧1 天前
使用Docker构建Node.js应用的详细指南
java·后端
码事漫谈1 天前
《C语言点滴》——笑着入门,扎实成长
后端
Tony Bai1 天前
【Go模块构建与依赖管理】09 企业级实践:私有仓库与私有 Proxy
开发语言·后端·golang
咖啡教室1 天前
每日一个计算机小知识:ICMP
后端·网络协议
间彧1 天前
OpenStack在混合云架构中通常扮演什么角色?
后端
咖啡教室1 天前
每日一个计算机小知识:IGMP
后端·网络协议
间彧1 天前
云原生技术栈中的核心组件(如Kubernetes、Docker)具体是如何协同工作的?
后端
清空mega1 天前
从零开始搭建 flask 博客实验(3)
后端·python·flask
努力的小郑1 天前
Elasticsearch 避坑指南:我在项目中总结的 14 条实用经验
后端·elasticsearch·性能优化