有关CORS跨域访问,这事没完
前后端分离模大势所趋,跨域问题更是老生常谈。
1. 跨域访问的问题背景
浏览器最基本的安全规范: 同源策略。所谓同源是指域名、协议、端口相同。
不同源的浏览器脚本(javascript、fetch方法,ActionScript、canvas)在没有明确授权的情况下,不能读写对方的资源。
CORS就是w3c和浏览器厂商为解决跨域资源共享问题而推出的标准方案: https://www.cnblogs.com/JulianHuang/p/10337980.html
① 浏览器遇到跨域请求,浏览器会自动携带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-Methods和Access-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响应码。
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
源码在https://github.com/Kong/kong/blob/255d4a1fad88082c13a875fdf1a70ceb4e4ea457/kong/plugins/cors/handler.lua#L210
如果你手痒设置成true,那么预检请求就会转发到upstream,需要后端自己去忽略对预检Options请求的认证。
② 预检之后实际的请求,不会再包含Access-Control-Request-** 请求头,只会有Origin请求头了。
我们看到触发预检时,一次AJAX请求会消耗掉两个TTL,严重影响性能。
2.3 节省掉Options预检请求,以提升性能
从上文可以看出,有两个方案:
方案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的示例:
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));
        });
     });
}
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/14225515.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化 
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号