Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 557|回复: 0

第十四节:Asp.Net Core 中的跨域解决方案(Cors、jsonp改造、chrome配置)

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-7-9 16:05:20 | 显示全部楼层 |阅读模式

    一. 整体说明

    1. 说在前面的话  

      早在前面的章节中,就详细介绍了.Net FrameWork版本下MVC和WebApi的跨域解决方案,详见:https://www.cnblogs.com/yaopengfei/p/10340434.html 由于在Core版本中,MVC和WebApi已经合并,所以在该章节中介绍Asp.Net Core中的跨域解决方案。

    2. 背景

      浏览器出于安全性考虑,禁止在网页上发出请求到不同的域的web页面进行请求,此限制称为同域策略。 同域策略可阻止恶意站点读取另一个站点中的敏感数据, 但有时候,你可能想要允许其他站点对您的应用程序进行跨域请求,所以有两套解决方案,分别是:CORS(跨域资源共享) 、 jsonp。

    PS:有时候为了方便测试,可以直接配置一下Chrome浏览器,使其支持跨域请求。

    3. 跨域资源共享(CORS)

      它是一项 W3C 标准,主流浏览器都支持,原理是让服务器放宽同域策略,它很灵活,可以显式的控制允许哪些地址、哪种请求方式来进行跨域访问。

    4. JSONP

       简单的来说,它就是在json数据外面包了一层,它有一个很大的局限性,就是仅支持Get请求,如下JSON和JSONP的区别:

    (1) json格式:
       {
      "id":123,
      "name":"ypf"
       }
    (2) jsonp格式:在json外面包了一层
    callback({
      "id":123,
      "name":"ypf"
      })
      其中callback取决于url传到后台是什么,它就叫什么。

    5. 浏览器配置

     有时候为了测试方便,我们可以直接配置一下Chrome浏览器,使其支持跨域。

     

    二. 跨域资源共享

    1. 说明

      使用的程序集是【Microsoft.AspNetCore.Cors】,在Core Mvc中,默认已经包含了,无须再引入;他有两种作用形式,可以在Configure管道中UseMvc前进行全局拦截,也可以在以特性的形式作用于控制器 或 Action。

    2. Cors策略详解

     A. 设置允许的来源:WithOrigins 或 AllowAnyOrigin        如:WithOrigins("http://localhost:29492","http://localhost:5000")

     B. 设置允许的方法:WithMethods 或 AllowAnyMethod   如:WithMethods("GET","PUT")

     C. 设置允许的请求标头:WithHeaders 或 AllowAnyHeader

     D. 设置公开响应标头:WithExposedHeaders

     E. 允许请求中的凭据:AllowCredentials

    PS:AddCors注册服务,AddDefaultPolicy注册默认策略、AddPolicy注册命名策略

    3. 使用流程

     流程一: 在ConfigureService先注册策略(默认策略或命名策略),然后可以在Configure管道中进行全局拦截 或者 以特性的形式作用于Controller或action。

     流程二: 直接在Configure中配置相应的策略进行全局拦截,不需要在ConfigureService中注册任何代码。

    4. 默认策略测试

    (1). 前提

      在Configure中通过AddDefultPolicy注册默认策略,策略内容:允许所有来源、所有方法、所有请求标头、允许请求凭据。代码如下:

     1  public void ConfigureServices(IServiceCollection services)
     2         {
     3             services.Configure<CookiePolicyOptions>(options =>
     4             {
     5                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.
     6                 options.CheckConsentNeeded = context => true;
     7                 options.MinimumSameSitePolicy = SameSiteMode.None;
     8             });
     9 
    10             //注册跨域请求服务
    11             services.AddCors(options =>
    12             {
    13                 //注册默认策略
    14                 options.AddDefaultPolicy(builder =>
    15                 {
    16                     builder.AllowAnyOrigin()
    17                            .AllowAnyMethod()
    18                            .AllowAnyHeader()
    19                            .AllowCredentials();
    20                 });           
    21             });
    22             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    23         }

    (2). 测试1-全局拦截

       在ConfigureService中(app.UseMvc前)通过app.UseCors();注册默认策略,然后在前端访问Test1方法,正常访问。

    PS:Test1方法要注释掉cors特性后再测试。

     1      public void Configure(IApplicationBuilder app, IHostingEnvironment env)
     2         {
     3             if (env.IsDevelopment())
     4             {
     5                 app.UseDeveloperExceptionPage();
     6             }
     7             else
     8             {
     9                 app.UseExceptionHandler("/Home/Error");
    10             }
    11 
    12             app.UseStaticFiles();
    13             app.UseCookiePolicy();
    14 
    15             //全局配置跨域(一定要配置在 app.UseMvc前)
    16             //1. 默认策略
    17             app.UseCors();
    18 
    19             app.UseMvc(routes =>
    20             {
    21                 routes.MapRoute(
    22                     name: "default",
    23                     template: "{controller=Home}/{action=Index}/{id?}");
    24             });
    25         }
    1         /// <summary>
    2         /// 默认策略
    3         /// </summary>
    4         /// <returns></returns>
    5         [HttpPost]
    6         public string Test1()
    7         {
    8             return "ypf001";
    9         }
    1           //1. 测试cors默认策略
    2             $('#j_btn1').click(function () {
    3                 $.post("http://localhost:29492/Home/Test1", {}, function (data) {
    4                     alert(data);
    5                 });
    6             });

    (3). 测试2-特性作用

      在Test1方法上增加特性[EnableCors],然后进行测试,正常访问。

    PS:ConfigureService中全局拦截要注释掉再测试

     1         /// <summary>
     2         /// 默认策略
     3         /// </summary>
     4         /// <returns></returns>
     5         [EnableCors]
     6         [HttpPost]
     7         public string Test1()
     8         {
     9             return "ypf001";
    10         }

    5. 命名策略测试

    (1). ConfigureService中策略的注册代码

     1       public void ConfigureServices(IServiceCollection services)
     2         {
     3             services.Configure<CookiePolicyOptions>(options =>
     4             {
     5                 // This lambda determines whether user consent for non-essential cookies is needed for a given request.
     6                 options.CheckConsentNeeded = context => true;
     7                 options.MinimumSameSitePolicy = SameSiteMode.None;
     8             });
     9 
    10             //注册跨域请求服务
    11             services.AddCors(options =>
    12             {
    13                 //注册一个名字为“AnotherPolicy”新策略
    14                 options.AddPolicy("AnotherPolicy", builder =>
    15                 {
    16                     builder.WithOrigins("http://localhost:29553", "http://localhost:5001")  //多个地址通过"逗号"隔开
    17                           .WithMethods("GET","PUT")
    18                           .WithHeaders(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "Authorization")
    19                           .AllowCredentials();
    20 
    21                 });
    22 
    23             });
    24          services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    25         }

    (2). Configure全局拦截代码  或者  作用于Action的代码 (二者选其一即可)

    全局拦截:

     1      public void Configure(IApplicationBuilder app, IHostingEnvironment env)
     2         {
     3             if (env.IsDevelopment())
     4             {
     5                 app.UseDeveloperExceptionPage();
     6             }
     7             else
     8             {
     9                 app.UseExceptionHandler("/Home/Error");
    10             }
    11             app.UseStaticFiles();
    12             app.UseCookiePolicy();
    13 
    14             //全局配置跨域(一定要配置在 app.UseMvc前)
    15             //2. 命名策略
    16             app.UseCors("AnotherPolicy");
    17 
    18             app.UseMvc(routes =>
    19             {
    20                 routes.MapRoute(
    21                     name: "default",
    22                     template: "{controller=Home}/{action=Index}/{id?}");
    23             });
    24         }

    作用于action

     1         /// <summary>
     2         /// 命名策略
     3         /// </summary>
     4         /// <returns></returns>
     5         [EnableCors("AnotherPolicy")]
     6         [HttpPost]
     7         public string Test2()
     8         {
     9             return "ypf002";
    10         }

    (3). 前端测试代码

     1           //2. 测试cors命名策略
     2             $('#j_btn2').click(function () {
     3                 $.ajax({
     4                     url: 'http://localhost:29492/Home/Test2',
     5                     type: "Post",
     6                     xhrFields: {
     7                         //发送跨域请求凭据
     8                         withCredentials: true
     9                     },
    10                     cache: false,
    11                     data: {},
    12                     beforeSend: function (request) {
    13                         //在请求报文头中加入Authorization 目的是让请求为非简单请求
    14                         request.setRequestHeader("Authorization", "Bearer 071899A00D4D4C5B1C41A6B0211B9399");
    15                     },
    16                     success: function (data) {
    17                         alert(data);
    18                     }
    19                 });
    20             });

    6. 直接Configure全局配置测试

      直接在Configure管道中配置策略,默认允许所有,注释掉ConfigureService中的所有策略和Test1、Test2方法上的特性, 仍然可以正常访问,测试通过。

     1       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
     2         {
     3             if (env.IsDevelopment())
     4             {
     5                 app.UseDeveloperExceptionPage();
     6             }
     7             else
     8             {
     9                 app.UseExceptionHandler("/Home/Error");
    10             }
    11 
    12             app.UseStaticFiles();
    13             app.UseCookiePolicy();
    14 
    15             //全局配置跨域(一定要配置在 app.UseMvc前)
    16             //3. 直接配置策略,不需在Configure中注册任何代码了
    17             app.UseCors(options =>
    18             {
    19                 options.AllowAnyOrigin()
    20                         .AllowAnyMethod()
    21                         .AllowAnyHeader()
    22                         .AllowCredentials();
    23             });
    24 
    25             app.UseMvc(routes =>
    26             {
    27                 routes.MapRoute(
    28                     name: "default",
    29                     template: "{controller=Home}/{action=Index}/{id?}");
    30             });
    31         }

    特别注意: 

      在core 2.x版本,AllowAnyOrigin()和AllowCredentials()可以共存,在3.x版本,不能共存。

     

    三. JSONP

    1. 原始的jsonp模式

      在Asp.Net Core中支持,在.Net版本的webapi中是不支持,即在方法中声明一个接受参数与前端JSONP位置传递过来的进行对应,然后将数据进行包裹返回,$"{myCallBack}({xjs})"。以JQuery的Ajax请求为例,可以指定JSONP的名称,如下面代码指定为:myCallBack,如果省略,JSONP的名称则为:callback。

    特别注意:凡是JSONP请求,GET请求地址的第一个参数则为JSONP位置配置的名称,如:http://localhost:29492/Home/GetInfor2?myCallBack=jQuery341001790888637311383_1564124488832&userName=ypf&_=1564124488833

    服务器端代码:

     1        /// <summary>
     2         /// 原始的JSONP模式,支持
     3         /// </summary>
     4         /// <param name="callBack"></param>
     5         /// <param name="userName"></param>
     6         /// <returns></returns>
     7         [HttpGet]
     8 
     9         public dynamic GetInfor1(string myCallBack, string userName)
    10         {
    11             var data = new
    12             {
    13                 id = userName + "001",
    14                 userName = userName
    15             };
    16             string xjs = JsonConvert.SerializeObject(data);
    17             return $"{myCallBack}({xjs})";
    18         }

    前端代码:

     1         $('#j_jsonp1').click(function () {
     2                 $.ajax({
     3                     url: 'http://localhost:29492/Home/GetInfor1',
     4                     type: "get",
     5                     dataType: "jsonp",
     6                     //需要和服务端回掉方法中的参数名相对应
     7                     //注释掉这句话默认传的名称叫callback
     8                     jsonp: "myCallBack",
     9                     cache: false,
    10                     data: { userName: "ypf" },
    11                     success: function (data) {
    12                         console.log(data);
    13                         alert(data.id + "," + data.userName);
    14                     }
    15                 });
    16             });

    2. 利用过滤器判断请求来决定是否跨域

    (1) 背景

      想实现服务器端方法正常写,该返回什么就返什么,如果是JSONP请求,则返回JSONP格式,如果是普通请求,则返回不同格式。

    (2) 原理

      如果是jsonp请求的,如同下面这个地址:http://localhost:29492/Home/GetInfor2?myCallBack=jQuery341001790888637311383_1564124488832&userName=ypf&_=1564124488833, 第一个参数为myCallBack,如果有这个参数则证明是jsonp请求,需要拿到它的值;反之如果没有这个参数,则是普通请求,需要配置跨域策略了,才能拿到值。

      在过滤器中判断,如果是jsonp请求,则将数据进行包裹进行返回,如果是普通请求,则直接返回,然后把过滤器以特性[IsJsonpCallback]形式作用在GetInfor2方法上。

    过滤器代码:

     1  public class IsJsonpCallbackAttribute: ActionFilterAttribute
     2     {
     3         private const string CallbackQueryParameter = "myCallBack";
     4         public override void OnActionExecuted(ActionExecutedContext context)
     5         {
     6 
     7             string text = context.HttpContext.Request.QueryString.Value;
     8             string[] arrys = text.Split('&');
     9             if (arrys[0].Contains(CallbackQueryParameter))
    10             {
    11                 var myCallBackValue = arrys[0].Split('=')[1];
    12                 var result = $"{myCallBackValue}({((ObjectResult)context.Result).Value})";
    13                 context.HttpContext.Response.WriteAsync(result);
    14             }
    15 
    16             base.OnActionExecuted(context);
    17         }
    18     }

    服务器端代码:

     1      /// <summary>
     2         /// 改造后的Jsonp(请求是Jsonp格式,则返回jsonp格式,反之普通格式)
     3         /// </summary>
     4         /// <param name="callBack"></param>
     5         /// <param name="userName"></param>
     6         /// <returns></returns>
     7         [HttpGet]
     8         [IsJsonpCallback]
     9 
    10         public dynamic GetInfor2(string userName)
    11         {
    12             var data = new
    13             {
    14                 id = userName + "001",
    15                 userName = userName
    16             };
    17             string xjs = JsonConvert.SerializeObject(data);
    18             return $"{xjs}";
    19         }

    前端代码:

     1   //2. 利用过滤器改造-jsonp请求
     2             $('#j_jsonp2').click(function () {
     3                 $.ajax({
     4                     url: 'http://localhost:29492/Home/GetInfor2',
     5                     type: "get",
     6                     dataType: "jsonp",
     7                     //需要和服务端回掉方法中的参数名相对应
     8                     //注释掉这句话默认传的名称叫callback
     9                     jsonp: "myCallBack",
    10                     cache: false,
    11                     data: { userName: "ypf" },
    12                     success: function (data) {
    13                         console.log(data);
    14                         alert(data.id + "," + data.userName);
    15                     }
    16                 });
    17             });
    18             //3. 利用过滤器改造-普通请求
    19             $('#j_jsonp21').click(function () {
    20                 $.ajax({
    21                     url: 'http://localhost:29492/Home/GetInfor2',
    22                     type: "get",
    23                     dataType: "json",
    24                     cache: false,
    25                     data: { userName: "ypf" },
    26                     success: function (data) {
    27                         console.log(data);
    28                         alert(data.id + "," + data.userName);
    29                     }
    30                 });
    31             });

    3. 利用程序集【WebApiContrib.Core.Formatter.Jsonp】改造

      GitHub地址:https://github.com/WebApiContrib/WebAPIContrib.Core/tree/master/src/WebApiContrib.Core.Formatter.Jsonp

      通过Nuget引入上面程序集,在ConfigureServices中的AddMvc中,书写代码 option => option.AddJsonpOutputFormatter(),即可实现JSONP请求返回JSONP格式,普通请求返回不同格式, 默认情况下回调参数为“callback”,也可以手动配置:option =>option.AddJsonpOutputFormatter(mvcOption.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault(), "myCallBack")

    默认回调参数配置的代码:

    1   services.AddMvc(    
    2        //不写参数的话,走的是默认回调参数 callback
    3        option => option.AddJsonpOutputFormatter()
    4   ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    命名回调参数配置的代码:

    1  MvcOptions mvcOption = new MvcOptions();
    2  services.AddMvc(    
    3     //配置JSONP的方案二,回调参数配置为myCallBack
    4     option =>option.AddJsonpOutputFormatter(mvcOption.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault(), "myCallBack")
    6   ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    通过上面的代码配置,即可实现JSONP请求返回JSONP格式,普通请求返回不同格式。(原理去github上看代码)

     

    四. Chrome浏览器配置

    1. 前提

      注释掉所有策略、特性,通过下面的步骤设置Chrome浏览器的属性,使其支持跨域。

    2. 配置步骤

     (1).在电脑上新建一个目录,例如:C:\MyChromeDevUserData

     (2).在Chrome根目录中找到exe程序,在其属性页面中的目标输入框里加上 --disable-web-security --user-data-dir=C:\MyChromeDevUserData,其中--user-data-dir的值就是刚才新建的目录。

    如下图:

     

    配置内容为:"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir=C:\MyChromeDevUserData,--user-data-dir

     (3).点击应用和确定后关闭属性页面,并打开chrome浏览器。再次打开chrome,发现有“--disable-web-security”相关的提示,如下图:,说明chrome可以正常跨域工作了。

    3. 测试

    (1). 服务器端方法 和 前端请求代码 如下:

    1         /// <summary>
    2         /// 不进行任何配置,通过配置浏览器进行跨域
    3         /// </summary>
    4         /// <returns></returns>
    5         public string TestNoConfig()
    6         {
    7             return "ypfTestNoConfig";
    8         }
    1            //1. 配置浏览器进行跨域
    2             $('#j_brower').click(function () {
    3                 $.post("http://localhost:29492/Home/TestNoConfig", {}, function (data) {
    4                     alert(data);
    5                 });
    6             });

    (2). 先用普通浏览器进行请求,请求不通,如下图:

    (3). 再用配置好跨域的chrome进行请求,请求通了,如下图:

     

     

     

     

     

     

     

     

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 22:17 , Processed in 0.063460 second(s), 31 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表