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访问控制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容