web开发中的浏览器跨域整理

HTTP访问控制(CORS)

同源策略
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
为什么不能跨域访问
跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是CSRF跨站攻击原理,无论是否跨域,请求是发送到了后端服务器,注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。所以只有服务器允许跨域,并且在相应包的头信息里面指明允许跨域,那么跨域请求的响应数据就不会被浏览器拦截丢弃了。

什么是跨域

URL1 URL2 是否跨域 原因
http://kangbiao.org/index https://kangbiao.org/index 协议不同
http://kangbiao.org/index http://kangbiao.org:8080/index 端口号不同
http://kangbiao.org/index http://baidu.org/index 主机不同
http://kangbiao.org/index http://t1.kangbiao.org/index 主机不同

通过上面的比较可以归纳出,跨域是指协议、主机地址、端口号这三个条件只要有一个不同则认为是跨域。

六种跨域方式

通过浏览器对象解决

document.domain(适用于子域跨域)
在同源策略中有一个例外,脚本可以设置 document.domain
的值为当前域的一个后缀,如果这样做的话,短的域将作为后续同源检测的依据。例如,假设在 http://t1.kangbiao.org/index 中的一个js脚本执行了下列语句:
document.domain = "kanbgiao.org";
这条语句执行之后,页面将会成功地通过对 http://company.com/index
的同源检测。但是不能通过设置 document.domain = "notkangbiao.org";完成对其他域的访问,该方法只适用于子域和父域之间的跨域解决。

使用document.domain来让子域安全地访问其父域,需要同时将子域和父域的document.domain设置为相同的值,没有这么做的话会导致授权错误。

window.name
这种方案实用性不高,实现也挺麻烦,也不够灵活,所以我就不详细写了,有兴趣可以参考这篇文章

客户端和服务端配合实现jsonp

jsonp其实就是动态创建js脚本。虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的函数,因此可以将script的src属性设为需要跨域的接口地址,但是需要服务器将数据组装成js变量定义或者函数传回来,举例如下:
比如kangbiao.org/index 需要调用t1.kangbiao.org/getServerInfo接口获取服务器信息,原来该接口的返回是:

{
  "ip":"192.168.1.1",
  "cpu":"Intel i5",
  "network":"100M"
}

现在为了配合jsonp的话返回格式应该如下:

  var response={
  "ip":"192.168.1.1",
  "cpu":"Intel i5",
  "network":"100M"
};

所以jsonp就是在返回数据中定义一个js变量或者函数来实现动态创建js脚本,这样做的缺点也显而易见,会出现变量污染或者函数重名(可以通过生命一个服务器专用的函数对象解决),而且服务器和前端脚本变量绑定太强,不是很灵活。

采用HTML5中的postMessage解决

postMessage可以实现窗口和窗口,页面和iframe,页面和窗口间的跨域通信。
postMessage需要源网站和跨域网站同时实现两个接口postMessage(发送数据)和addEventListener(监听事件,接受数据)

otherWindow.postMessage(message, targetOrigin);

otherWindow
其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames

message
将要发送到其他 window的数据。

targetOrigin
通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的orign属性完全一致,来防止密码被恶意的第三方截获。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

target.addEventListener(type, listener[, useCapture]);

type
表示所监听事件类型的一个字符串。

listener
当指定的事件类型发生时被通知到的一个对象。该参数必是实现
EventListener
接口的一个对象或函数。

useCapture
可选
如果值为true, useCapture表示用户希望发起捕获。 在发起捕获之后, 只要Dom子树下发生了该事件类型,都会先被派发到该注册监听器,然后再被派发到Dom子树中的注册监听器中。并且向上冒泡的事件不会触发那些发起捕获的事件监听器。进一步的解释可以查看 DOM Level 3 Events 文档。 请注意该参数并不是在所有的浏览器版本中都是可选的。如果没有指定, useCapture默认为false 。

两个函数的定义如上,addEventListener不是html5中特有的,postMessage是html新增实现跨域通信的。
如果需要跨域交换数据,则需要两边都需要同时实现这两个接口,才能交换数据,不然只能单方向的接收或者发送数据。一般的实现是在addEventListener的回掉函数中通过event.data获取到传过来的数据后,再次调用postMessage将处理后的数据返回给消息来源对象。这样实现好处就是完全不需要后端的参与。但是有一定的安全风险,配合xss可以导致用户凭证信息被盗取。
具体的代码示例参考postMessage实示例

服务器响应头控制(CORS 跨域资源共享)

这种方法是我认为最好的方法,由服务器决定是否允许跨域,如果允许,服务器在响应头中添加相应的字段告诉浏览器此次跨域合法,则浏览器不会将请求包丢弃(文章开头说了跨域其实是浏览器的一种行为),从而完成跨域。
这种方法的详细操作我就不多说了,参考廖雪峰的这篇文章
主要叙述下服务端怎么设置响应头
在PHP中可以中国header()函数设置允许跨域字段
在java中可以通过设置reponse.setHeader()函数来设置,spring4.2及以上版本提供了@CrossOrigin注解来方便实现跨域。

服务器代理

服务器代理就是将需要跨域访问的地址通过服务器访问(服务器此时作为客户端,不会受同源策略限制),然后由服务器返回结果。
例如kangbiao.org/index 页面需要访问api.weibo.com/getNews 来获取最新新闻,我们可以通过在kangbiao.org的服务器上面多增加一个接口 kangbiao.org/api?url=api.weibo.com/getNews ,然后再服务器内部,该接口所做的事情就是向api.weibo.com/getNews 发起请求即可,然后将结果返回。
这样做的好处是实现十分简单,而且可以访问任何跨域站点,缺点就是需要新增维护一个接口,而且如果服务器是通过代理网关,只能内网通信的话也很麻烦。

反向代理

反向代理也是在服务器实现的,主要是通过正则匹配url,匹配成功后重写到目标地址即可,这种方法可以实现所有网站的跨域,不需要服务器提供跨域支持,个人认为比较方便,甚至可以配置kangbiao.org/proxy/weibo/实现将api.weibo.com域的接口整合到我们自己的网站下面来,并且程序不需要做任何改动,改下nginx的配置文件即可。具体的实现方案很多,google或者百度nginx反向代理实现跨域即可。

参考文章

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

推荐阅读更多精彩内容