ASP.NET Core MVC中的跨域Cors详解

在一些项目中,我们经常会用到跨域,在jquery ajax时代,还采用的jsonp的方式进行跨域请求。但这种方式的安全性很低,api端对所有请求均开放了。

如今,NET CORE发展也是越来越完善了,对于跨域,也有完善的策略。

今天我们就来详细讲讲 跨域 到底是怎么一回事儿。

以下示例均以 JQuery  的ajax 为例。其他xhr 方式的异步请求,雷同。

浏览器安全性可防止网页向不处理网页的域发送请求。 此限制称为同域策略。 同域策略可防止恶意站点从另一站点读取敏感数据。

浏览器默认采用的是同域策略。但有时,我们可能希望允许其他网站向自己的应用发出跨源请求。

我们浏览器请求的方法有:PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH 这几个。

实际上我们跨域请求某个API 时,浏览器是发起了2个请求,一个是OPTIONS 方法,一个是实际的数据请求方法(如GET/POST/PUT等)。

OPTIONS 请求,就像是探子一样,先去问一下,是否允许跨域请求,若允许,OK,再实际发送请求数据。 如下请求示例的截图;

 

 

 只有OPTIONS状态正常,我们的POST请求才能正确获取到API返回的数据。否则,将报 CORS 跨域异常。

知道了原理之后,我们先看一个简单的中间件的处理方式。既然是给OPTIONS 请求正确的响应就行,那我们自定义一个中间件来模拟。

跨域还跟几个请求头的配置有关系,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,即我们可以针对这几个请求头进行跨域策略的配置。下面会详细讲解。

我们来看这个中间件怎么写

  public class CorsMiddleware
    {
        private readonly RequestDelegate next;

        public CorsMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Headers.ContainsKey(CorsConstants.Origin))
            {
                //定义4个请求头相关配置
                context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers["Origin"]); //必须配置,此处写法作用等于*,可以将context.Request.Headers["Origin"]直接用"*"代替,表示允许所有的请求。或者可配置只允许的域名,多个用英文逗号隔开 如 https://www.1633.com,http:1633.com
                //context.Response.Headers.Add("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH"); //可以不配置
                //context.Response.Headers.Add("Access-Control-Allow-Headers", context.Request.Headers["Access-Control-Request-Headers"]); //可以不配置
                //context.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); //可以不配置

                if (context.Request.Method.Equals("OPTIONS"))
                {
                    context.Response.StatusCode = StatusCodes.Status200OK;
                    return;
                }
            }

            await next(context);
        }
    }

然后使用Startup.cs  Configure 方法中调用中间件

  app.UseMiddleware<CorsMiddleware>();

这个中间件,主要就2个配置

1:配置允许的来源头,Access-Control-Allow-Origin ,若配置为* ,表示 允许任意来源的请求。

2:当请求方法是OPTIONS 时,直接进行 200 正常响应。

验证方式也简单,上面我们提到了以往我们ajax请求跨域,需要用jsonp,这次我们服务端已经加了允许跨域了,就直接用json请求发起。

服务端接口,我们在 Home控制器下定义了Test3 方法,返回一个常规的json格式的内容。

        public JsonResult Test3()
        {
            var data = new { code = 3 };
            return Json(data);
        }

把项目运行起来。然后我们在自己本机桌面上,创建一个 test.html文档,引入jquery

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</head>
  
    <body>
        <input id="test3" type="button" value="测试3"/>

    </body>
<script>


    $("#test3").bind("click",function(){
           $.ajax({
            url:"http://localhost:52842/home/test3",
            type:"get",
            dataType:"json",
            error:function(){alert("请求失败");},
            success:function(json){
                        alert(json.code);
            }
           })

    });
</script>

</html>

直接打开这个html文档,点击按钮

 

 

 说明,我们跨域请求成功了。

假如我们没调用这个跨域中间件,则会出现下图结果:

 

 

OK,上面是以自定义中间件的方式来开篇的,下面才是我们重点要介绍的内容。。。

如何使用NETCore自带的Cors中间件来设置跨域策略??? 

以下示例是基于.NET6.0的环境,低版本在startup.cs中写同样的代码即可。

主要是调用服务的AddCors进行跨域的配置

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); //MyPolicy 为自定义的策略名称,与使用时相同即可。可以同时定义多个不同策略名称的跨域策略
});

然后调用中间件执行

app.UseCors("MyPolicy"); //指定调用MyPolicy 配置的策略,表示全局都采用该策略

这样,我们就实现了一个很简单的,全局的跨域策略。

项目运行起来,可以正常跨域。

但,如果你觉得这样就可以直接用到项目里面去,那安全性就太低了。

上面的示例中,我们允许了所有的来源。这明显是不安全的。而且知道的跨域配置项,还有不少。

下面,我们就详细讲下各个配置项,也可参考微软官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?source=recommendations&view=aspnetcore-7.0

AllowAnyOrigin:允许具有任何方案(http 或 https)的所有源的 CORS 请求。 AllowAnyOrigin 不安全,因为 任何网站 都可以向应用发出跨域请求。

AllowAnyMethod:允许所有的请求方法。

AllowAnyHeader:允许所有请求头。若不配置这个,当增加我们自定义的请求头时,跨域就会失败。

 AllowCredentials:允许有凭据,默认是无,匿名都可以。它不能跟AllowAnyOrigin共存。若是要允许所有的来源,还配置该项,那需要把 .AllowAnyOrigin() 替换成.SetIsOriginAllowed(_=>true)

 

以上4个均是允许所有的请求,不做任何限制。在实际项目中,AllowAnyOrigin,我们是不建议允许所有来源。

那实际项目中,我们应该如何配置,才能既实现跨域,又具有一定的安全性呢?我们采用以下几个属性来分别替代

WithOrigins:可替代AllowAnyOrigin方法,可以设定只允许跨域请求的来源域名,域名包含 http部分,最后不能以 /结尾,允许配置多个。

这样,非我们配置的域名,就不能跨域来访问我们的资源。

WithMethods:配置允许请求的方法。比如 .WithMethods("POST","GET"),表示只允许POST和GET的请求,其他请求不允许。

WithHeaders:表示请求的Access-Control-Allow-Headers头必须是与WithHeaders配置的相等,必须是相等才行,包含也不行。一般情况下,我们可以不配置这个。而是采用AllowAnyHeader。

这样,配置如下

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.WithOrigins("null", "http://localhost:5151", "https://www.baidu.com")
    .WithMethods("POST","GET")
    .WithHeaders("kywtoken")
    );
});

若实际项目中仅有 来源需要配置,那可以这样

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.WithOrigins("null", "http://localhost:5151", "https://www.baidu.com")
   
    );
});

若我们的场景是允许同一个主域下的所有子域名都可以跨域请求,则配置的来源可以设置为通配符。同时 增加 SetIsOriginAllowedToAllowWildcardSubdomains()方法,如下:

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetIsOriginAllowedToAllowWildcardSubdomains()
   
    );
});

这样,整个.1633.com的子域名都生效。

还有一个方法,SetPreflightMaxAge

这个方法的意思是可以设置OPTIONS的缓存时间,啥意思呢?
上面我们讲到了,每次发起一个跨域请求时,其实浏览器都会转成2个请求。 1个是OPTIONS 请求,一个是实际的请求。那对服务器来说,就是2次请求了。

为了降低服务器的负载,我们可以通过 配置SetPreflightMaxAge 来告知浏览器,OPTIONS请求多长时间有效,不用每次都发个OPTIONS来咨询了。直接发请求过来就可以给你响应了。

如下:

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetIsOriginAllowedToAllowWildcardSubdomains().SetPreflightMaxAge(TimeSpan.FromHours(1))

    ); 
});

我们配置了缓存1小时,也就是1小时内,同个域名的请求,不会重新发起OPTIONS 请求的。就减少了请求的次数。

 

所以综上,我们一般可以这么配置:

 services.AddCors(options =>
{
    options.AddPolicy("MyPolicy", p => p.WithOrigins("https://*.1633.com").SetPreflightMaxAge(TimeSpan.FromHours(1)).AllowAnyHeader().AllowAnyMethod().AllowCredentials()
    ); 
});

主要就是配置来源了,其他参数根据项目需要改配置即可。

以上,是全局生效的做法,我们也可以选择部分生效,包括禁用Cors,选择哪些路由或者控制器、aciont生效。

具体参考微软官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?source=recommendations&view=aspnetcore-7.0

 

引申点:

有了这个跨域请求,我们就可以不需要使用JSONP来处理了,可直接像JSON一样即可。

以前JSONP还只能GET请求,对于增删改操作不合适。

现在有了跨域,也可以采用POST发起ajax请求了。

==========================================

更多分享,请大家关注我的个人公众号:

 

posted @ 2022-12-07 17:19  黄明辉  阅读(1759)  评论(0编辑  收藏  举报