弱弱的问一个js跨域调用的问题的问题

最近由于项目需要,在开发一系列浏览器插件,涉及的浏览器包括Chrome,Firefox和IE。在插件的实现中,部分功能需要通过跨域的API调用完成,这会导致一些问题,而问题在IE浏览器中尤为突出。
首先要说明的是,跨域访问是不可避免的。原因是我们开发的是插件而不是页面,API的调用都是在当前页面的环境中完成的,而我们的API服务器是自己内部的域名。这个问题在Chrome和Firefox的插件开发中不难处理,可以在相应的配置文件(如ChromeExtension的manifest.json)中允许某个域的访问即可,例如:
&name&: &My extension&,
&permissions&: [
&http://*./&
我推断这两种浏览器插件允许跨域访问的原因是,插件中的脚本是在一套独立的脚本环境中执行的,而不是在当前页面的真实环境,因此即便通过跨域获得的资源有风险,也不会污染和危害当前页面的真实环境。(参见)相反,对于IE浏览器来说,没有类似的沙盒机制,插件中注入的脚本就是运行在当前页面的真实上下文中,API获得的资源直接可以控制当前页面的行为。因此IE浏览器为了安全考虑,对跨域访问的限制十分严格。
这里还有一个小问题,就是所谓限制跨域资源访问,到底保护的是谁的利益?这个问题应该从服务器和客户端两个方面理解。对于服务器,当收到一个请求时,会检查该请求来源,如果来源的客户端页面自己无法识别,而且服务器的数据又是比较敏感的,则可能做出限制,或者干脆拒绝访问(例如,黑客从他自己的site攻击你的服务器)。对于客户端,在请求前可能要检查这个请求的目标服务器是不是能够识别或者信任的,对于与当前页面所在域不同的服务器,浏览器可能会使用同源策略(same
origin policy)进行限制甚至拒绝访问(例如,黑客通过让你访问他的服务器数据来攻击你的客户端页面)。在理解跨域访问的问题时,首先要清楚的事情就是此次访问的风险是来自哪里又作用到哪里。
我先从客户端说起。浏览器的同源策略可以限制对跨域资源的访问。在RFC标准中一个源(origin)的定义如下:
(协议schema://域名domain:端口port)
因此,以上三部分只要有一个不同,浏览器就会认为是跨域资源访问。IE浏览器稍有不同,它的同源策略不会检查端口号。但是IE浏览器对协议不同产生的跨域请求的限制十分严格。以我开发时调试使用的IE
11浏览器(Windows 10)为例,如果从一个使用HTTPS协议的site访问一个HTTP协议的API(假设后者有权威机构颁发的数字证书),就会有问题,通常是报错:Access
is denied。因为这是从安全的site访问不安全的site,IE考虑安全因素会abort该请求,对于ajax来说,是在XmlHttpRequest对象的open方法中抛出异常。如果使用HTTPS的API服务器没有提供证书,或者证书不可信任,则API调用会失败,这对所有浏览器都是如此。原因很好解释,没有证书的HTTPS站点可能被攻击者仿冒,后面会详细解释数字证书的设计动机和原理。以IE浏览器为例,发起ajax请求会报错:
XMLHttpRequest: Network Error0x2ee4, Could not complete the operation due to error
一个workaround方法是,手动在客户端安装证书到浏览器的信任机构列表(Internet
Options-&Content-&Certificates-&Trusted Root CA),或者在浏览器地址栏访问一次api,然后在警告时选择信任该站点。但这不是解决问题的根本方法,最好还是申请一个权威机构颁发的证书(StartCom也提供了免费版的信任证书,不过只能针对外网的域名)。
上述证书的错误,并不是在所有版本的浏览器上都会出现。比如在Windows 8.1上的IE
11,在第一次请求时,浏览器会提示是否信任服务器的的证书,因为浏览器无法验证该证书。如果信任,则后续跨域API可以正常调用。其实数字证书存在的理由及其原理已经不属于跨域访问的范围了,但是考虑到从HTTP站点访问HTTPS站点仍是跨域访问,而HTTPS协议又离不开数字证书,这里就简单说一下我对证书的理解。
我们知道HTTPS协议实际上是在HTTP协议下层,在TCP协议上层,引入一个安全协议层,通常是SSL/TLS。目的是对要传输的数据进行一些处理,如加密解密,数字签名校验等,以保证传输数据的安全性和完整性。HTTPS使用对称加密算法对通信明文进行加密,而对称加密使用的密钥是在SSL握手阶段由服务器和客户端协商生成的,关于SSL握手的详细过程可以参考这篇文章,里面图文并茂,阐释详细明晰。第一个安全问题是:服务器客户端协商的密钥如何防止被窃取?SSL使用非对称的公钥加密算法对密钥进行机密后传输的。具体地,客户端通过通信两端产生的三个随机数生成一个密钥,并使用服务器的公钥进行加密,发送给服务器,服务器使用自己的私钥解密得到这个密钥。由于私钥只有服务器自己知道,因此密钥是安全的。第二个安全问题是:客户端如何知道他在与真正的目标服务器进行通信?攻击者很可能将自己的公钥发给客户端,然后冒充服务器进行通信。这就是数字证书存在的原因。数字证书就是由权威机构颁发的用来证明服务器身份的二进制文件。证书的内容包含了颁发机构、证书有效期、公钥加密和数字签名算法等信息。客户端拿到证书后,只要使用权威证书机构的公钥解密就得到证书内容。如果证书不是受信任的,则浏览器无法得到证书内容,即无法验证证书的合法性,就会发出警告。第三个安全问题是:虽然公钥加密是安全的,但是如何保证证书内容不被篡改呢?SSL使用数字签名机制保证数据的完整性。数字签名算法也是个非对称加密算法,服务器在证书中包含了数字签名算法的相关信息,由于服务器在对证书签名时使用了签名算法的私钥,因此在客户端使用签名算法的公钥就能得到明文内容的校验和,进而验证证书是否被篡改或仿冒。以上这些机制就保证了HTTPS通信的安全性。
有了这些原理的储备,就理解了HTTP通信的脆弱之处,就可以更好的理解为什么IE浏览器不允许从HTTPS站点访问HTTP协议的API。
对于服务器来说,每当遇到跨域资源访问的请求,需要与客户端进行沟通。这种沟通通过CORS(Cross-Origin
Resource Sharing 跨源资源共享)来实现。CORS是W3C的一个草案,定义了必须访问跨源资源时,服务器和浏览器应该如何使用HTTPheader进行沟通,从而决定请求或响应是否成功。例如,浏览器发送一个简单的GET或POST请求,如果是跨域的,需要在请求中加入一个Origin
header,用来告诉服务器请求的源信息:
服务器会根据这个header决定是否响应该请求,如果可以接受,则会在响应header中加入:
Access-Control-Allow-Origin:
以上只是简单的请求,如果用户需要自定义header或者使用其他method,就不是附加header能搞定的了。这时需要一个Preflight请求与服务器沟通,该请求使用OPTIONS方法实现,通常会发送以下header:
Origin:与前面功能相同
Access-Control-Request-Method:请求使用的方法
Access-Control-Request-Headers:请求使用的自定义header
服务器收到Preflight请求后,依据允许的源、方法和header发送响应header,如:Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers等。这样客户端就知道服务器允许那些源、方和header了。由于Preflight会多发一次请求,因此浏览器会根据服务器返回的信息将Preflight响应缓存一段时间,用于后续的跨域请求。
常见的浏览器都通过XmlHttpRequest对象实现了CORS机制,而微软的IE
8引入了XDR(XDomainRequest)对象部分实现了CORS机制,但XDR从IE
11开始就不再支持。另外,IE 10以及之前版本不支持Preflight请求。据我的经验,IE
11会发送Preflight请求。
如果服务器不检查请求的来源会有什么安全问题呢?典型就是CSRF(Cross
SiteRequest Forgery)攻击。顾名思义,攻击者会伪造客户端的请求对服务器进行攻击。(请参看:https://blogs./ieinternals//same-origin-policy-part-2-limited-write/)一个常见的例子是,我们使用电商网站购物时(如在亚马逊),我们的账户认证信息以及我们浏览商品的记录信息会存储在客户端(cookies),当我们点击某个商品详情时,会向电商的服务器发出HTTP请求,同时存储在浏览器的认证信息也会发送到服务器用于验证用户身份。服务器验证了你的身份后,会记录你当前访问的页面的信息,方便日后为你推荐一些商品。当你使用浏览器时,攻击者可能会诱导你点击某个站点的链接(如论坛中的某条评论),而这个链接的隐藏页面的后台会向你经常访问的电商网站发送一个HTTP请求(比如请求一个手机商品详情页面),由于你的账号信息记录在浏览器中,所以这次请求可以成功,而服务器也会相应记下你访问过这个页面。随后你浏览电商网站时,就可能看到那个手机商品的推荐,尽管你从未访问过那个商品。
服务器对跨域访问的检查就可以有效避免或限制这个行为。因为正常访问和伪造请求的区别就在于请求的源页面是否是服务器能够识别的页面。
以上就是我近期对跨域资源访问以及安全性的一点理解和小结。除了文中的链接,还参考了《HTTP权威指南》、《JavaScript高级程序设计》二书。
本文已收录于以下专栏:
相关文章推荐
关于Ajax跨域访问问题的一种解决办法
日期:日  作者:雨水
Ajax跨域是前端开发中常见的问题,本文描述了以Google浏览器Chrome作为客户端和以Tomcat作为W...
1 错误场景
今天要把项目部署到外网的时候,出现了这样的问题, 我把两个项目放到自己本机的tomca...
人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..
整理了同源策略限制,出现了跨域请求的需求,原生传统的跨域请求方式,原生js和jQuery中对跨域的处理(JSONP),以及HTML5 中的CORS跨域资源共享。系统条理得梳理好传说中的跨域了!o(^▽...
深入理解AJAX系列第四篇--跨域问题
他的最新文章
讲师:董西成
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)主题 : 一个网页调试和跨域的问题,大家都来看看
可可豆: * CB
威望: * 点
在线时间: (时)
注册时间: *
最后登录: *
发自: Web Page
一个网页调试和跨域的问题,大家都来看看&&&
我们服务端是用java写的,通讯是通过 ,get,post……访问服务端的数据我用cocos - js&&里面的 cc.loader.getXMLHttpRequest 遇到安全域问题我的开发模式是,webStorm + 谷歌浏览器请问你们有没有遇到同样的问题?怎么解决的是不是只有网页上会出现这样的问题,app上正常呢?贴上我的方法Http.prototype.sendPostPlainText = function(url,data,callBack,errorCallBack ) {&&&&var xhr = cc.loader.getXMLHttpRequest();&&&&streamXHREventsToLabel(xhr,&POST&);&&&&xhr.open(&POST&,url);&&&&xhr.setRequestHeader(&Content-Type&,&application/x-www-form-charset=UTF-8&);&&&&&&&&&& xhr.send( data);}
级别: 新手上路
可可豆: 70 CB
威望: 50 点
在线时间: 84(时)
发自: Web Page
我也遇到过这个问题,如果你是拿自己服务器的数据或文件的话,就方便多了,可以修改下访问权限。如果是拿别人服务器的东西,就复杂了。至于打包成app是不是正常,同问。不过我觉得应该没问题,平台都不一样了嘛。
可可豆: * CB
威望: * 点
在线时间: (时)
注册时间: *
最后登录: *
发自: Web Page
回 1楼(chookg) 的帖子
怎么修改访问权限呢?服务端是自己的。
级别: 新手上路
可可豆: 70 CB
威望: 50 点
在线时间: 84(时)
发自: Web Page
回 2楼(白桦树) 的帖子
随便搜了下,你可以参考下
可可豆: * CB
威望: * 点
在线时间: (时)
注册时间: *
最后登录: *
发自: Web Page
回 3楼(chookg) 的帖子
这个是针对flash的,网页跟这个还不太一样。
可可豆: * CB
威望: * 点
在线时间: (时)
注册时间: *
最后登录: *
发自: Web Page
问题解决了。如果用网页调试确实有这个问题,我今天上午装了一个ide2.0 用模拟器调试,这个问题是不存在的。希望给能帮助到新手,请默默给我点个赞吧。
级别: 骑士
UID: 309749
可可豆: 1314 CB
威望: 957 点
在线时间: 458(时)
发自: Web Page
回 5楼(白桦树) 的帖子
哥们,浏览器上的问题解决了没?模拟器上运行是没问题的,因为他不用浏览器解析啊!
级别: 新手上路
可可豆: 278 CB
威望: 278 点
在线时间: 44(时)
发自: Web Page
兄弟问题如何解决的, 我也是用 webStorm + 谷歌浏览器,但是我是用cocos2d-js 的socket.io 连java的服务器出现跨域问题。
关注本帖(如果有新回复会站内信通知您)
发帖、回帖都会得到可观的积分奖励。
按"Ctrl+Enter"直接提交
关注CocoaChina
关注微信 每日推荐
扫一扫 关注CVP公众号
扫一扫 浏览移动版标签:至少1个,最多5个
决定js框架
后我决定用coffeescript来看一些js框架,本来想用react,结果发现用它跟gulp配起来略烦,选来选去选择了小半天决定最终用 , 看起来比较小,而且源码有coffee和js两个版本,还方便看,当然我知道这不是什么主流框架,可能文档什么的少一些,但这样更可以看出看源码的重要性,无论是不是python。
好的,开始学习
首先就丢到一边,基于脚手架建了一个项目 , 由于我把bower,npm的安装文件放到git中管理,项目比较大,github传输太慢了,先放着开源中国的git上。
然后bower装了下spine,npm装了下spine,我知道bower装的东西会被gulp编译到vendor.js里面,然而貌似我不用npm装spine的话在coffee里面会require报错(我暂时没有管require到底干毛的,看起来是import),bower装的spine有个问题,因为gulp中采用了一个main-bower-file还是什么东西的找包的主文件,然后gulp讲这个文件粘贴到vendor.js里面,但是看起来spine是模块分离的,需要单独引用(或者是其他什么原因),它并没有bower.json。所以我修改了下gulp中的tasks/bower.coffee将所有的外部库丢到一个文件夹里面这样可以直接引用(有没有更好的方案之后再说)。
第一步目录结构
spine是mvc的,然而都是js,所以我在source下面建了controllers、models、views这三个文件夹,views里面采用这么种东西,看起来很像djang的模板。
class Contact extends Spine.Model
@configure "Contact", "name"
@extend Spine.Model.Ajax
@url: "/users
class App extends Spine.Controller
constructor: -&
# Instantiate other controllers..
Photo.fetch()
根据这个文档我知道了继承Model后@extend一个Spine.Model.Ajax然后添加一个url就可以调用Model.fetch()了,so我在main.coffee里面试了一下然后就出了些事情,容我慢慢道来。
我准备用的某些api做一些事情: ,下面是我的Model。
class Course extends Spine.Model
@configure "Course", "name"
@extend Spine.Model.Ajax
@url: "/api/v2/courses"
module.exports = Course
fecth调用的时候首先404了,因为gulp在dev时启动的是一个localhost的本地服务器(改成0.0.0.0:3000了), spine在请求的时候url是相对路径所以拼上的,好的吧,那我写绝对路径 @url: /api/v2/courses, 然而他依然是拼接的(),我丢你老母。
然后继续看文档发现了 Spine.Model.host = "http://my-endpoint",强行把host设走,我就在main.coffee第一行把host设置了学堂在线的host,然后 就跨域了。
喜闻乐见的跨域问题
首先我们知道,跨域是浏览器的某种安全限制,服务器端发起request并不存在跨域问题(这也是你可以愉快的用脚本requests.get或者写爬虫的原因)。解决跨域问题有很多方案:
如果外域本来就是你的,并且你觉得允许所有人访问的话(一般不用,出发你就是一个专门的api服务器),nginx配上三行代码即可(一搜就有)
同1,只是不是nginx上加,而是在代码里面做一些事情,例如django,用
chrome可以关闭跨域 启动的时候加上一个参数即可C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe --disable-web-security
写一个proxy,这样你就有了1 2两点的条件随便搞
我选择的是4+2
proxy server
根据上面的方案选择我在项目根目录用django建了一个simple_server
cd 你的项目位置/leaning-frontend/simple_server
pip install -r requirements.txt # 如果你懂python请用virtualenv什么的
./manage.py runserver 0.0.0.0:12345 # 12345是你想运行的端口号
urls.py, 只有一个url所有的请求都用CrossDomainAjaxView来处理
url(r'^(?P&request_url&.*)$', CrossDomainAjaxView.as_view(), name='ajax-hanler')
views.py, 这个view是做这么一件事情的,将请求的url通过requests这个三方库在我的服务器端请求外域服务器,然后讲response转变成django的response来返回给前端,暂时只写了get(非get django要穿csrf什么的比较烦)。这里有件事情想讲一下:
请求的header,我本来是想把接受到的所有header丢给requests的然而获取不到数据,暂时不管;
response的header,我本来也是想把所有的response header都带回来的,然而并不行,python级别的东西报了一个http 1.1有关的异常is_hop_by_hop,所以我也暂时没带过来。
rest framework仅仅是我想用他的view支持下非get post请求(然而我并没做具体处理,实际上没有任何作用)
# -*- coding: utf-8 -*-
#from rest_framework.views import View
#from rest_framework.generics import GenericAPIView as View
from django.views.generic import View
from django.conf import settings
from simple_server.utils import get_request_header, get_request_headers
from urlparse import urljoin
from django.http.response import HttpResponse
from wsgiref.util import is_hop_by_hop
import requests
class CrossDomainAjaxView(View):
def transform_request_data(self, request, request_url):
cross_domain_host = get_request_header(request, 'cross_domain_host') or settings.CROSS_DOMAIN_HOST
url = urljoin(cross_domain_host, request_url)
headers = get_request_headers(request)
data = dict(request.GET)
data.update(dict(request.POST))
request_data = {
'cross_domain_host': cross_domain_host,
'url': url,
'data': data,
'headers': headers,
return request_data
def transform_response(self, requests_response):
response = HttpResponse(requests_response.content, content_type=
requests_response.headers.get('Content-Type', 'text/plain'))
return response
def get(self, request, request_url):
request_data = self.transform_request_data(request, request_url)
response = requests.get(request_data['url'], request_data['data'])
#headers=request_data['headers'])
print response.content
return self.transform_response(response)
settings.py
middleware加上这两个来允许跨域'corsheaders.middleware.CorsMiddleware'、CORS_ORIGIN_ALLOW_ALL = True, 然后由于前端传的是相对路径所以我加了一个host CROSS_DOMAIN_HOST = '', get的时候支持在header里面改变不同的host支持不同网站的api调用而不是写死一个。
chrome的Network的XHR里面这次看到请求了,哇哈哈拦路虎解决。
oh对了,顺便fix了一些gulp对于spine需要支持的东西,主要是eco。
btw,发现学堂在线一个 (好吧其实是产品强行让我贴的友链,各位随意)
nginx可以添加location来直接通过反向代理跨域
location /xuetangx {
rewrite ^.+xuetangx/?(.*)$ /$1
include uwsgi_
proxy_pass /;
location /videoid2source/ {
#include uwsgi_
proxy_pass /videoid2source/;
1 收藏&&|&&9
你可能感兴趣的文章
9 收藏,1.4k
4 收藏,1.4k
16 收藏,2.9k
分享到微博?
技术专栏,帮你记录编程中的点滴,提升你对技术的理解收藏感兴趣的文章,丰富自己的知识库
明天提醒我
我要该,理由是:14615人阅读
Language&Framework(1)
Javascript(1)
什么是跨域
跨域是指从一个域名的网页去请求另一个域名的资源。比如从 页面去请求
的资源。跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域
为什么浏览器要限制跨域访问呢?
原因就是安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:
用户访问 ,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器
用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站
这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对 的操作。
如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。
为什么要跨域
既然有安全问题,那为什么又要跨域呢? 有时公司内部有多个不同的子域,比如一个是 ,而应用是放在 , 这时想从 去访问
的资源就属于跨域。
跨域访问需要的两件宝贝
由于浏览器一般不对script,img等进行跨域限制,所以我们有机会通过script的方式来实现跨域访问。
跨域访问需要用到两样东东,一个是JSON,一种基于文本的传输协议;一种是JSONP,一群码农想出来的跨域解决方案。关于JSON与JSONP的解释,可以参考
实现跨域访问 服务端需要做什么
服务端要检查访问的请求参数,如果没有callback,则可以按照之前的流程走;如果带着callback参数,则需要将返回的结果包装在callback里面。
比如请求的URL是: /location?callback=myCallback , 那么服务端则需要把结果封装进myCallback 函数里面, 如下
if (params.query && params.query.callback) {
params.query.callback + '(' + JSON.stringify(data) + ')';
res.end(str);
res.end(JSON.stringify(data));
实现跨域访问 客户端需要做什么
客户端有多种方式可以实现JSONP的调用:
jQuery可以在Ajax里面设置datatype为jsonp,则可以进行跨域访问
$scope.jqueryJsonpRequest = function(){
jQuery.ajax({
type: "get",
async: false,
url: "https://public-/rest/v1/sites//posts",
dataType: "jsonp",
jsonp: "callback",
jsonpCallback:"flightHandler",
success: function(json){
alert('success' + JSON.stringify(json));
error: function(){
alert('fail');
AngularJS的$http 也提供了对jsonp的访问,直接调用jsonp进行跨域访问
$http.jsonp('https://public-/rest/v1/sites//posts?callback=JSON_CALLBACK')
.success(function(data){
alert('success:'+data);
}).error(function(err){
alert('error:'+err);
不管是jQuery也好,AngularJS也罢,底下都不是发起XHR (XML HTTP Request),而都是通过加载javascript的方式来做的,所以如果项目没有依赖jQuery或者AngularJS,则可以自己手动实现jsonp的调用。
原理很简单,就是用javascript动态加载一个script文件,同时定义一个callback函数给script执行而已。
var myCallbackFunction = function(data){
alert('uuu:'+JSON.stringify(data));
window.myCallbackFunction = myCallbackF
var script = document.createElement('script');
script.src = 'https://public-/rest/v1/sites//posts?callback=myCallbackFunction';
document.body.appendChild(script);
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:17219次
排名:千里之外
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 解决跨域问题的方案 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信