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


一. 整體說明

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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM