ASP.NET中Web API解决跨域问题
一、什么是跨域问题
跨域:指的是浏览器不能执行其他网站的脚本。是由浏览器的同源策略造成的,是浏览器施加的安全限制。(服务端可以正常接收浏览器发生的请求,也可以正常返回,但是由于浏览器的安全策略,浏览器不能处理服务端的返回)。
那么什么是同源策略呢?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
正是由于这个原因,如果是在不同的项目之间进行调用(这里说的调用指的是浏览器调用后端,如果是后端直接调用就不会存在跨域问题)就会被浏览器阻止。WebApi中常见的场景:Web Api作为单独的数据服务层,提供接口供前端界面调用,MVC项目作为显示层,这种情况下如果在MVC的前端界面里面通过ajax调用WebApi的接口,就会存在跨域问题。
二、如何解决跨域问题
网上有很多跨域问题的解决方案,这里就不在一一列举了,下面主要讲解一下在WebApi中如何使用CORS解决跨域问题。CORS全称Cross-Origin Resource Sharing,中文全称是跨域资源共享。CORS解决跨域问题的原理是在http的请求报文和响应报文里面加入响应的标识告诉浏览器能够访问哪些域名的请求。
三、使用代码解决跨域问题
下面结合一个具体的实例来讲解在WebApi里面如何使用CORS解决跨域问题。
1、场景描述
新建两个单独的项目:一个WebApi项目(带有MVC功能,用来提供数据和页面显示),一个MVC项目(只是负责页面显示),项目结构如下图所示:
其中,WebApi项目的端口号是:33982,MVC项目的端口号是:34352(这两个端口号是在本机的地址,在其他电脑上端口号可能不同)。显而易见:两个项目的端口号不同,不属于同源,如果在MVC里面通过前端调用WebApi提供的数据接口,就会出现跨域的问题。
2、项目结构
2.1 WebApi项目结构
新建WebApiController文件夹,用来存放WebApi控制器,并新建一个WebApi控制器,命名为Student。StudentController控制器的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using WebApi.Entity; namespace WebApi.WebApiController { public class StudentController : ApiController { public static List<Student> studentList = InitStudentList(); private static List<Student> InitStudentList() { return new List<Student>() { new Student {StudentID =1,StudentName="唐僧",Age=24,Sex="男",Major="师范"}, new Student {StudentID =2,StudentName="孙悟空",Age=579,Sex="男",Major="管理"}, new Student {StudentID =3,StudentName="沙悟净",Age=879,Sex="男",Major="水利工程"}, new Student {StudentID =4,StudentName="白骨精",Age=456,Sex="女",Major="表演"}, new Student {StudentID =5,StudentName="玉兔精",Age=456,Sex="女",Major="舞蹈"} }; } [HttpGet] public IHttpActionResult GetAllStudent() { return Json<List<Student>>(studentList); } } }
修改WebApi配置文件类,路由规则里面增加action,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; namespace WebApi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
新建一个MVC控制器,命名为Student,并添加Index视图,Index视图代码如下:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>测试跨域问题</title> <script src="~/Scripts/jquery-1.10.2.min.js" type="text/javascript"></script> </head> <body> <div style="background-color:#008000;padding:10px;margin:5px;"> <div style="font-weight:bold;margin-bottom:5px;">Get Student List</div> <div style="padding-bottom:5px;"> <input id="btnGetStudentList" name="btnGetStudentList" type="button" value="Get Student List" /> </div> <div id="students"></div> </div> <script> $('#btnGetStudentList').click(function () { $.ajax({ url: '/api/Student/GetAllStudent', type: 'GET', dataType: 'json' }).success(function (result) { DisplayStudentList(result); }).error(function (data) { alert(data); }); }); function DisplayStudentList(result) { var studentTable = $("<table cellpadding='3' cellspacing='3'></table>"); var studentTableTitle = $("<tr><th>StudentID</th><th>StudentName</th><th>Age</th><th>Sex</th><th>Major</th></tr>"); studentTableTitle.appendTo(studentTable); for (var i = 0; i < result.length; i++) { var studentTableContent = $("<tr><td>" + result[i].StudentID + "</td><td>" + result[i].StudentName + "</td><td>" + result[i].Age + "</td><td>" + result[i].Sex + "</td><td>" + result[i].Major + "</td></tr>" ); studentTableContent.appendTo(studentTable); } $('#students').html(studentTable); } </script> </body> </html>
2.2 MVC项目结构
MVC项目结构比较简单,新建一个名为Student的控制器,并添加视图,视图如下:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>测试跨域问题</title> <script src="~/Scripts/jquery-1.10.2.min.js" type="text/javascript"></script> </head> <body> <div style="background-color:#008000;padding:10px;margin:5px;"> <div style="font-weight:bold;margin-bottom:5px;">Get Student List</div> <div style="padding-bottom:5px;"> <input id="btnGetStudentList" name="btnGetStudentList" type="button" value="Get Student List" /> </div> <div id="students"></div> </div> <script> $('#btnGetStudentList').click(function () { $.ajax({ url: 'http://localhost:33982//api/Student/GetAllStudent', type: 'GET', dataType: 'json' }).success(function (result) { DisplayStudentList(result); }).error(function (data) { alert("失败"); }); }); function DisplayStudentList(result) { var studentTable = $("<table cellpadding='3' cellspacing='3'></table>"); var studentTableTitle = $("<tr><th>StudentID</th><th>StudentName</th><th>Age</th><th>Sex</th><th>Major</th></tr>"); studentTableTitle.appendTo(studentTable); for (var i = 0; i < result.length; i++) { var studentTableContent = $("<tr><td>" + result[i].StudentID + "</td><td>" + result[i].StudentName + "</td><td>" + result[i].Age + "</td><td>" + result[i].Sex + "</td><td>" + result[i].Major + "</td></tr>" ); studentTableContent.appendTo(studentTable); } $('#students').html(studentTable); } </script> </body> </html>
四、测试
1、在不做任何处理情况下的测试
先看看同源下的访问情况,直接启动WebApi项目,截图如下:
点击按钮,测试结果如下:
因为是在同一个域中,所以访问没有问题,前端可以正常获取到WebApi返回的数据。下面在来看看在MVC项目中的测试情况。测试截图如下:
从图中可以看出访问失败了,按F12查看访问情况:
从上面的截图中可以看出,发生了跨域访问,浏览器出于安全性的考虑,不能接收返回的数据。
五、使用CORS解决跨域问题
1、安装CORS
在WebApi项目上右键->管理NuGet程序包,然后搜索“microsoft.aspnet.webapi.cors”,选择第一个进行安装
具体的安装过程在这里不再详细解释。
2、配置跨域
在WebApiConfig.cs类中配置跨域,修改后的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; using System.Web.Http.Cors; namespace WebApi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 跨域配置 config.EnableCors(new EnableCorsAttribute("*","*","*")); // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
这里使用“*”号表示对所有的域名都可以跨域。再次从MVC里面访问WebApi,查看测试结果:
从上面的截图中可以看出,这时就可以允许跨域访问了,在Response Headers里面添加了:Access-Control-Allow-Origin:*。
六、CORS参数详解
在上面我们在WebApi的配置文件中使用了:
config.EnableCors(new EnableCorsAttribute("*","*","*"));
这一句代码解决了跨域问题。但是这种“*”号是不安全的。查看MSDN,发现EnableCorsAttribute类有如下的构造函数:
public EnableCorsAttribute( string origins, string headers, string methods )
知道了EnableCorsAttribute类的构造函数以后,我们可以使用下面的方法进行改进。
方法一:在Web.Config文件的appSettings节点里面配置参数:
然后修改WebApiConfig.cs文件的Register方法:
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; using System.Web.Http.Cors; using System.Configuration; namespace WebApi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { #region 跨域配置 string allowedOrigin = ConfigurationManager.AppSettings["allowedOrigin"]; string allowedHeaders = ConfigurationManager.AppSettings["allowedHeaders"]; string allowedMethods = ConfigurationManager.AppSettings["allowedMethods"]; config.EnableCors(new EnableCorsAttribute(allowedOrigin, allowedHeaders, allowedMethods)); #endregion // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
方法二:如果只想对某些api或者api里面的某些方法做跨域,可以直接在API控制器类上面使用特性标注或者在方法上面使用特性标注。
允许Student控制器可以跨域:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using WebApi.Entity; using System.Web.Http.Cors; namespace WebApi.WebApiController { [EnableCors(origins:"http://localhost:34352",headers:"*",methods:"GET,POST,PUT,DELETE")] public class StudentController : ApiController { public static List<Student> studentList = InitStudentList(); private static List<Student> InitStudentList() { return new List<Student>() { new Student {StudentID =1,StudentName="唐僧",Age=24,Sex="男",Major="师范"}, new Student {StudentID =2,StudentName="孙悟空",Age=579,Sex="男",Major="管理"}, new Student {StudentID =3,StudentName="沙悟净",Age=879,Sex="男",Major="水利工程"}, new Student {StudentID =4,StudentName="白骨精",Age=456,Sex="女",Major="表演"}, new Student {StudentID =5,StudentName="玉兔精",Age=456,Sex="女",Major="舞蹈"} }; } [HttpGet] public IHttpActionResult GetAllStudent() { return Json<List<Student>>(studentList); } } }
只允许Student控制器里面的GetAllStudent方法可以跨域:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using WebApi.Entity; using System.Web.Http.Cors; namespace WebApi.WebApiController { public class StudentController : ApiController { public static List<Student> studentList = InitStudentList(); private static List<Student> InitStudentList() { return new List<Student>() { new Student {StudentID =1,StudentName="唐僧",Age=24,Sex="男",Major="师范"}, new Student {StudentID =2,StudentName="孙悟空",Age=579,Sex="男",Major="管理"}, new Student {StudentID =3,StudentName="沙悟净",Age=879,Sex="男",Major="水利工程"}, new Student {StudentID =4,StudentName="白骨精",Age=456,Sex="女",Major="表演"}, new Student {StudentID =5,StudentName="玉兔精",Age=456,Sex="女",Major="舞蹈"} }; } /// <summary> /// 允许跨域 /// </summary> /// <returns></returns> [EnableCors(origins: "http://localhost:34352", headers: "*", methods: "GET,POST,PUT,DELETE")] [HttpGet] public IHttpActionResult GetAllStudent() { return Json<List<Student>>(studentList); } /// <summary> /// 不允许跨域 /// </summary> [HttpPost] public void Post() { } } }