[CORS:跨域资源共享] 同源策略与JSONP


Web API普遍采用面向资源的REST架构,将浏览器最终执行上下文的JavaScript应用Web API消费者的重要组成部分。“同源策略”限制了JavaScript的跨站点调用,这必然导致Web API不能垮域提供资源。如果Web API仅限于为“同源客户端”提供资源,那么它都对不起自己的名字,因为Web本身是一个开放的协议。那么ASP.NET Web API通过怎样的方式来实现跨域资源共享呢?

同源策略

浏览器是访问Internet的工具,也是客户端应用的宿主,它为客户端应用提供一个寄宿和运行的环境。而这里所说的应用,基本是指在浏览器中执行的客户端JavaScript程序。虽然是一种解释性的脚本语言,JavaScript其实是无比强大的,原则上来讲它可以做任何事。但是在能够在JavaScript脚本并不都是值得信赖的,所以浏览器必须对JavaScript的执行作相应的限制,这就是我们接下来着重介绍的“同源策略(Same Origin Policy)”。

同源策略是浏览器的一项最为基本同时也是必须遵守的安全策略,毫不夸张地说,浏览器的整个安全体系均建立在此之上。同源策略的存在,限制了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。所谓的“同源”,必须要求相应的URI在如下3个方面均是相同的。术语“源(Origin)”在中文表达中显得有点突兀,所以在接下来的内容中,我们更多地会采用“站点(Site)”或者“域(Domain)”这样的说法,在未作特别说明的情况下均与“源”表达相同的意思。

  • 主机名称(域名/子域名或者IP地址)
  • 端口号
  • 网络协议(Scheme,分别采用“http”和“https”协议的两个URI被视为不同源)

值得一提的是,对于一段JavaScript脚本来说,其“源”与它存储的地址无关,而取决于脚本被加载的页面。比如我们在某个页面中通过如下所示的

   2: 

除了




    
   5: body>
   6: html>

如果请求地址“http://localhost:3721/api/contacts?callback=listContacts”能够返回如下的内容,即返回的不是以JSON表示的数据,而是针对该数据的方法调用,毫无疑问联系人列表能够顺利呈现在页面上。这种将JSON对象填充(Padding)到某个JavaScript回调方法将数据转换成针对数据的操作语句的形式就是JSONP(JSON Padding)。

   1: listContacts([{"Name":"张三","PhoneNo":"123","EmailAddress":"zhangsan@gmail.com"},{"Name":"李四","PhoneNo":"456","EmailAddress":"lisi@gmail.com"},{"Name":"王五","PhoneNo":"789","EmailAddress":"wangwu@gmail.com"}]);

为了让定义在ContactsController的Action方法GetAllContacts返回如上所示的内容,我们可以对其相应的改动来完成。如下面的代码片断所示,我们将GetAllContacts的返回类型替换成HttpResponseMessage,其参数callback表示填充的JavaScript回调函数。在该方法中,我们利用JavaScriptSerializer对Contact列表对象进行序列化,并将得到的内容填充到回调函数中从而得到如上所示的内容。方法最终返回具有此主体内容的HttpResponseMessage对象,响应主体内容的媒体类型被设置为“text/javascript”。

   1: public class ContactsController : ApiController
   2: {
   3:     public HttpResponseMessage GetAllContacts(string callback)
   4:     {
   5:         Contact[] contacts = new Contact[]
   6:         {
   7:             new Contact{ Name="张三", PhoneNo="123", EmailAddress="zhangsan@gmail.com"},
   8:             new Contact{ Name="李四", PhoneNo="456", EmailAddress="lisi@gmail.com"},
   9:             new Contact{ Name="王五", PhoneNo="789", EmailAddress="wangwu@gmail.com"},
  10:         };
  11:         JavaScriptSerializer serializer = new JavaScriptSerializer();
  12:         string content = string.Format("{0}({1})", callback, serializer.Serialize(contacts));
  13:         return new HttpResponseMessage(HttpStatusCode.OK)
  14:         {
  15:             Content = new StringContent(content, Encoding.UTF8, "text/javascript")
  16:         };
  17:     }
  18: }

如果现在运行我们的程序,通过“跨域”(其实不是)调用Web API得到的联系人列表就会按照如右图所示的效果呈现出来。JSONP仅仅是利用