web跨域请求实战

同源策略

理解跨域首先必须要了解同源策略。同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。何谓同源:URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。

跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果。具体策略限制情况可看下表:

URL 说明
http://www.a.com/a.js http://www.a.com/b.js 同一域名下
http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名不同文件夹下
http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名不同端口
http://www.a.com/a.js https://www.a.com/b.js 同一域名不同协议
http://www.a.com/a.js http://127.0.0.100/b.js 域名和域名对应ip
http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不同
http://www.a.com/a.js http://a.com/b.js 同一域名,不同二级域名
http://www.a.com/a.js http://www.b.com/b.js 不同域名

只有文件协议、域名、端口和路径全部相同,这些文件才会是同源,同源文件间请求无须特殊处理,当不同源文件之间发生请求时,则需要跨域处理。

jsonp实现跨域原理

什么是jsonp?
参考百度百科,JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 json资料,而这种使用模式就是所谓的 jsonp。用 jsonp抓到的资料并不是 json,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 json解析器解析。

jsonp原理:
首先在客户端注册一个callback, 然后把callback的名字传给服务器。此时,服务器先生成 json 数据。然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp 。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)

jsonp作用:
由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出json数据并执行回调函数,从而解决了跨域的数据请求。

jsonp缺点:

  1. 它只支持GET请求而不支持POST等其它类型的HTTP请求(虽然采用post+动态生成iframe是可以达到post跨域的目的,但这样做是一个比较极端的方式,不建议采用)。
  2. jsonp易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,jsonp是非常合适的选择。可以看出来jsonp跨域一般用于获取其他域的数据。

一言不合上代码

获取json数据

使用jsonp获取json数据,类似同源post请求获取json数据,不过jsonp只支持get请求。

1. 客户端注册callback,并将callback名字传给服务器

$.ajax({ url: "http://localhost:8080" + "/my/order/cancel?orderNo=" + orderNo,    
 type: "get",        
 dataType: "jsonp",    
 jsonp: 'callback',   
 jsonpCallback: 'jsonp_callback',    
 success: function (data, status) {   
    //回调处理  
    alert(data);   
   }
});

上述代码,callback是回传至服务器参数,服务器使用callback参数拼接服务器端请求结果(json数据),返回给客户端。

*2. 服务器端处理请求 *

  @RequestMapping("/cancel")
  @ResponseBodypublic JSONPObject cancelOrder(String orderNo, HttpSession session, HttpServletRequest request, HttpServletResponse response, String callback) {   
   // 获取用户信息    
  Member member = (Member)   session.getAttribute(Constants.SESSION_MEMBER_NAME);   
  String operateIp = ClientIpUtil.getClientIp(request);    
  UserOrderOpCtx operateCtx = new UserOrderOpCtx(orderNo,String.valueOf(member.getId()),member.getNickName(),operateIp);   
   userOrderApiService.cancelOrder(operateCtx);
   response.setContentType("text/plain");    
   return new JSONPObject(callback, MapUtils.getMap(RespEnum.OK, "订单取消成功!"));   
}

根据请求客户端请求参数callback,组装jsonp数据,返回给客户端;

获取html数据

有些业务场景需要跨域获取其他系统页面数据,类似同源间get请求;
1. 客户端注册callback,并将callback名字传给服务器

$.ajax({ url: "http://localhost:8080" + "/my/order/cancel?orderNo=" + orderNo,    
 type: "get",        
 dataType: "jsonp",    
 jsonp: 'callback',   
 jsonpCallback: 'jsonp_callback',    
 success: function (data, status) {   
    //回调处理  
    alert(data);   
   }
});

上述代码,callback是回传至服务器参数,服务器使用callback参数拼接服务器端请求结果(json数据),返回给客户端。
*2. 服务器端处理请求 *

  @RequestMapping("/cancel")
  public void cancelOrder(String orderNo, HttpSession session,HttpServletResponse response, String callback) {   
   // 获取用户信息    
  Member member = (Member)   session.getAttribute(Constants.SESSION_MEMBER_NAME);   
  String operateIp = ClientIpUtil.getClientIp(request);    
  UserOrderOpCtx operateCtx = new     UserOrderOpCtx(orderNo,String.valueOf(member.getId()),member.getNickName(),operateIp);   
  userOrderApiService.cancelOrder(operateCtx);
  response.setContentType("text/plain");    
  response.getWriter().write(callback+ "JSON.toJSON(MapUtils.getMap(RespEnum.OK, "订单取消成功!"))"); //返回jsonp数据
}

HTTP访问控制

跨源资源共享(CROS)让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。需要特别注意的是,这个规范是针对API容器的。比如说,要使得XMLHttpRequest在现代浏览器中可以发起跨域请求。浏览器必须能支持跨源共享带来的新的组件,包括请求头和策略执行。同样,服务器端则需要解析这些新的请求头,并按照策略返回相应的响应头以及所请求的资源。

HTTP响应头

这部分里列出了跨域资源共享(Cross-Origin Resource Sharing)时,服务器端需要返回的响应头信息.上一部分内容是这部分内容在实际运用中的一个概述.

Access-Control-Allow-Origin
返回的资源需要有一个 Access-Control-Allow-Origin 头信息,语法如下:
Access-Control-Allow-Origin: <origin> | *
origin参数指定一个允许向该服务器提交请求的URI.对于一个不带有credentials的请求,可以指定为'',表示允许来自所有域的请求.
举个栗子,允许来自 http://mozilla.com 的请求,你可以这样指定:
Access-Control-Allow-Origin: http://mozilla.com
如果服务器端指定了域名,而不是'
',那么响应头的Vary值里必须包含Origin.它告诉客户端: 响应是根据请求头里的Origin的值来返回不同的内容的.

Access-Control-Expose-Headers
Requires Gecko 2.0(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)
设置浏览器允许访问的服务器的头信息的白名单:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
这样, X-My-Custom-Header
和 X-Another-Custom-Header这两个头信息,都可以被浏览器得到.

Access-Control-Max-Age
这个头告诉我们这次预请求的结果的有效期是多久,如下:
Access-Control-Max-Age: <delta-seconds>
delta-seconds
参数表示,允许这个预请求的参数缓存的秒数,在此期间,不用发出另一条预检请求.

Access-Control-Allow-Credentials
告知客户端,当请求的credientials属性是true的时候,响应是否可以被得到.当它作为预请求的响应的一部分时,它用来告知实际的请求是否使用了credentials.注意,简单的GET请求不会预检,所以如果一个请求是为了得到一个带有credentials的资源,而响应里又没有Access-Control-Allow-Credentials头信息,那么说明这个响应被忽略了.
Access-Control-Allow-Credentials: true | false

带有credential的请求在上面讨论.
Access-Control-Allow-Methods
指明资源可以被请求的方式有哪些(一个或者多个). 这个响应头信息在客户端发出预检请求的时候会被返回. 上面有相关的例子.
Access-Control-Allow-Methods: <method>[, <method>]*

发出预检请求的例子见上,这个例子里就有向客户端发送Access-Control-Allow-Methods响应头信息.

Access-Control-Allow-Headers
也是在响应预检请求的时候使用.用来指明在实际的请求中,可以使用哪些自定义HTTP请求头.比如
Access-Control-Allow-Headers: X-PINGOTHER
这样在实际的请求里,请求头信息里就可以有这么一条:
X-PINGOTHER: pingpong
可以有多个自定义HTTP请求头,用逗号分隔.
Access-Control-Allow-Headers: <field-name>[, <field-name>]*

HTTP 请求头

这部分内容列出来当浏览器发出跨域请求时可能用到的HTTP请求头.注意这些请求头信息都是在请求服务器的时候已经为你设置好的,当开发者使用跨域的XMLHttpRequest的时候,不需要手动的设置这些头信息.
Origin
表明发送请求或者预请求的域
Origin: <origin>

参数origin是一个URI,告诉服务器端,请求来自哪里.它不包含任何路径信息,只是服务器名.
Note: Origin的值可以是一个空字符串,这是很有用的.
注意,不仅仅是跨域请求,普通请求也会带有ORIGIN头信息.
Access-Control-Request-Method
在发出预检请求时带有这个头信息,告诉服务器在实际请求时会使用的请求方式
Access-Control-Request-Method: <method>

相关的例子可以在这里找到
Access-Control-Request-Headers
在发出预检请求时带有这个头信息,告诉服务器在实际请求时会携带的自定义头信息.如有多个,可以用逗号分开.
Access-Control-Request-Headers: <field-name>[, <field-name>]*
尊重版权,参考博客Ajax+Spring MVC实现跨域请求(JSONP),
HTTP访问控制

推荐阅读更多精彩内容