缘起
以前在asp.net mvc时代,很少出现跨域问题
自从使用了asp.net web api + angular (1/2)之后,开始有跨域问题了。
简单普及下跨域:
我的理解是只要是前台页面与后台服务不在同一域名下,或者同域名不同端口下,就是跨域,我做个真值表:
前端 | 后端 | 是否跨域 |
http://localhost:3000 | http://localhost:3000 | |
http://localhost:3000 | http://localhost:2000 | 跨 |
http://localhost:3000 | http://localhost:5000 | 跨 |
如果跨域,最直接的表现就如下图:
下面我帖上测试代码(未解决跨域问题之前):
前端:
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script type="text/javascript"> $(function () { $("#btn2").click(function () { $.ajax({ type: "POST", url: "http://localhost:4272/api/Resources/GetPrintMethod", success: function (data) { console.log(data); }, error: function (e, XMLHttpRequest, textStatus) { console.log(e); }, }); }); }); $(function () { //.ajaxError事件定位到document对象,文档内所有元素发生ajax请求异常,都将冒泡到document对象的ajaxError事件执行处理,ajax方法中有error,先处理error,再冒泡到此处的error $(document).ajaxError( //所有ajax请求异常的统一处理函数,处理 function (event, xhr, options, exc) { alert("StatusCode:" + xhr.status); if (xhr.status == 'undefined') { return; } switch (xhr.status) { case 403: // 未授权异常 alert("系统拒绝:您没有访问权限。"); break; case 404: alert("您访问的资源不存在。"); break; } } ); }); </script> <body> <input type="button" value="测试跨域请求" id="btn2"> </body>
后端就是一个asp.net web api程序,我就不全贴了,只挑重点了:
目前只有一个Controller:
using System.Collections.Generic; using System.Threading.Tasks; using System.Web.Http; namespace Summer.Api.Controllers { [RoutePrefix("api/Resources")] public class ResourcesController : ApiController { [Route("GetPrintMethod")] [HttpPost] [HttpGet] public async Task<IHttpActionResult> GetPrintMethod() { var list = new List<string> { "数据1", "数据2" }; return Ok(new { frontPrintMethod = list }); } } }
这时,我们运行起来后端,在Controller上打上断点:
然后前端请求:
首先,Controller断点生效,似乎正常通过,return Ok(.....),
But,前台却得到:
这是前端的一个异常捕获,status code是0,0是什么?这显然不是我们想要的
在 https://blog.csdn.net/zheng963/article/details/42237709 我查到了一些端倪:
竟然还没有调用Send方法?这是什么鬼?WTF...为什么会这样呢?
再观察下NetWork看看:
发现有1次请求,并且状态是200,照理说应当没问题了,可为什么会得到statuscode 0呢?
Console还有一行错误信息:
Failed to load http://localhost:4272/api/Resources/GetPrintMethod: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
先看一下详细信息再说:
200,OK,竟然还得到了数据,不错,不错,可是为什么有那行错误信息呢?
那行错误信息
Failed to load http://localhost:4272/api/Resources/GetPrintMethod: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
这段是说被请求的资源没有允许跨域,所以不允许访问,可是奇怪,不允许访问,为什么还能得到结果呢。。。奇怪。。。不知道是不是Bug。。。。。
尝试解决
下面我来尝试解决它
既然是跨域,那就找解决方案呗,后台是WebApi,NuGet查找并安装 Microsoft.AspNet.WebApi.Cors
然后在WebApiConfig中加上一句:
config.EnableCors(new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*"));
再次运行请求看下,这次我们成功得到了结果,并没有报错对比下加没加跨域插件下的响应报文头:
我发现加了跨域插件后,响应报文头,自动加了一行:
弦外
人就是不满足,现在,我要加需求,我Api现在有了一个要求:
现在我允许了跨域,你爱是哪来哪来,我都允许,但是。。。
(据说但是之前的都是XXX)
你必须要有合法的Token,Token是什么,如何获取,如何验证它是否合法,这里不介绍。
只是我要有一个Token,随便是什么,然后,API来模拟在Action执行前拦截进行Token校验。
先写一个 MyAuthHandler继承自 DelegatingHandler,具体代码如下:
1 using System; 2 using System.Net; 3 using System.Net.Http; 4 using System.Threading; 5 using System.Threading.Tasks; 6 using System.Web; 7 8 namespace Summer.Api.Providers 9 { 10 public class MyAuthHandler : DelegatingHandler 11 { 12 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 13 CancellationToken cancellationToken) 14 { 15 var authHeader = request.Headers.Authorization; 16 17 try 18 { 19 var token = authHeader.Scheme; 20 if (string.IsNullOrWhiteSpace(token)) 21 return request.CreateErrorResponse(HttpStatusCode.Forbidden, "invalid_grant. Token is empty."); 22 } 23 24 catch (Exception ex) 25 { 26 return request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message, ex); 27 } 28 29 return await base.SendAsync(request, cancellationToken); 30 } 31 }
然后把它加也加在WebApiConfig中:
在MyAuthHandler中我重写了SendAsync方法,这样每当服务器在执行真正Action之前都会先走这个方法,我就可以在这里进行请求合法性检测了。
运行前端再次请求,依然得到了StatusCode 0,和500错误,并且,跨域的问题又回来了:
好难缠啊,又跨域,似乎那插件没有对新加的Handler起作用。
分析下:
500,是因为请求Headers中没有Authorization,更加没有Token,所以我们直接返回
return request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message, ex);
这个OK
但为什么我没有得到这500,而却得到了0?
观察下响应头:
呃,问题原来在这里,响应头中又没有了:
Access-Control-Allow-Origin: *
好吧,插件不起作用,我手动加上总可以了吧
Handler中改一下:
再次调用查看结果:
Excellent, That's what I really needed.
终于得到了我想要的正确的Code问题完美解决。
尾声
回顾下过程:
1.出现跨域
2.引用Cros插件解决跨域
3.新增需求,要在Action执行前进行Token验证
4.新增MyAuthHandler : DelegatingHandler来在每一次执行Action前进行拦截检测
5.拦截后又出现跨域,Cros插件未对其起作用
6.手动为Response添加允许跨域响应头:
Access-Control-Allow-Origin: *
7.问题遭到解决
But:
为什么在没有加允许跨域时,虽然得到错误,却也得到了数据呢?
Angular
再次使用Angular2来调用WebApi
依然可以得到正确Code,跨域问题完美解决在 Asp.net Web Api
.net Core中似乎不存在这么复杂,没有继续测试
node里,其实更加简单,只要在每次返回时加上允许跨域头即可。