CORS
一、CORS内部机制与实现
CORS(Cross-Origin Resource Sharing,跨域资源共享),它允许浏览器向服务器发出XMLHttpRequest请求,从而克服AJAX只能同源使用的限制。
PS:同源:协议相同,域名相同,端口相同。
http://www.example.com/dir/page.html
,协议是http://
,域名是:/www.example.com
,端口是80
(默认省略)。非同源,Cookie、LocalStorage 和 IndexDB无法读取,DOM 无法获得,AJAX请求不能发送
http://www.example.com/dir2/other.html:同源http://example.com/dir/other.html:不同源(域名不同) http://v2.www.example.com/dir/other.html:不同源(域名不同)http://www.example.com:81/dir/other.html:不同源(端口不同)
CORS需要浏览器和服务器同时支持。目前使用XMLHttpRequest
实现的方式(仅支持异步),所有浏览器都支持,但IE10+,低版本的IE有其他实现方式。
CORS的背后思想:使用自定义的HTTP头部,让浏览器与服务器进行沟通,从而决定请求或响应是该成功还是应该失败。在发送请求时,浏览器自行附加一个Origin头部,包含发出请求页面的原信息(协议、域名和端口),然后服务器根据这个头部信息来决定是否给予响应。
客户端发出
Origin:http://www.abc.com
,如果服务器认为这个请求可以接受,就返回Access-Control-Allow-Origin:http://www.abc.com
,如果没有该返回头部,或者头部源信息不匹配,浏览器就会驳回请求。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源(请求的url跨域路径是绝对路径),就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
二、两种请求
CORS分为简单请求和非简单请求。
简单请求同时满足以下两大条件:
1.请求方式是以下三种方法之一:
HEAD ,GET, POST
2.HTTP的头部信息不超出以下几种字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值,application/x-www-from-urlencoded(表单序列化)、multipart/form-data(上传文件)、text/plain(纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理)
凡是不同时满足上面两个条件,就是非简单请求。浏览器的处理方式不一样。
三、简单请求
1.基础信息
简单请求,浏览器直接发出CORS请求。在头信息增加Origin
字段
浏览器简单AJAX跨域请求:
GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.com(接受请求的域) Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
服务器根据Origin
的值来决定是否同意请求。如果Origin
指定的源,不在许可范围内,服务器会返回一个正常的HTTP响应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果服务器接受 Origin
,头部信息会多出几个信息字段。
Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin
字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Contro
l、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar字段的值。
2.withCredentials 属性
CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段。
Access-Control-Allow-Credentials:true
另一方面,开发者必须在AJAX请求中打开withCredentials
属性。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,如果省略withCredentials
设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials
。
xhr.withCredentials = false;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
四、非简单请求
1.预检请求
非简单请求时对服务器有特殊要求的请求,例如请求方法是,PUT
(让服务器创建一个文件,文件名是请求的url,文件内容是请求报文的主体)|DELETE
(删除请求url指定的资源);或者Content-Type
字段是application/json
非简单请求,正式通信之前,增加一次HTTP查询请求,叫做“预检请求”
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用那些HTTP动词(请求方法)和头部信息字段。只有得到肯定的答复,浏览完才会正式发出XMLHttpRequest
请求,否则就报错。
JS脚本,PUT
请求,且发送一个自定义头信息X-Custom-Header
> var url = 'http://api.alice.com/cors'; > var xhr = new XMLHttpRequest(); > xhr.open('PUT', url, true); > xhr.setRequestHeader('X-Custom-Header', 'value'); > xhr.send();
浏览器发现,这是一个非简单请求,就自动发出一个“预检请求”,要求服务器确认允许这样的请求。下面是一个预检请求的头信息。
OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
“预检请求”使用的请求方法是OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header
。
2.预检请求回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8Content-Encoding: gzip Content-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-Alive Content-Type: text/plain
上面的HTTP回应中,关键的是Access-Control-Allow-Origin
字段,表示http://api.bob.com
可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
如果浏览器否定了预检请求,返回一个正常的HTTP回应,但是么有任何CROS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的回调函数捕获。
服务器回应的其他CORS相关字段如下。
Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。Access-Control-Max-Age
: 1728000
浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
> PUT /cors HTTP/1.1> Origin: http://api.bob.com> Host: api.alice.com > X-Custom-Header: value > Accept-Language: en-US > Connection: keep-alive > User-Agent: Mozilla/5.0...
上面头信息的Origin
字段是浏览器自动添加的。
下面是服务器正常的回应。
> Access-Control-Allow-Origin: http://api.bob.com> Content-Type: text/html; charset=utf-8
上面头信息中,Access-Control-Allow-Origin
字段是每次回应都必定包含的。
五、跨浏览器的CORS实现
所有浏览器都支持简单请求(非预检请求和不带凭据的请求),因此有必要做跨浏览器方案。检测XHR是否支持CORS的最简单方式就是就是检查xhr中是否存在withCredentials属性。再结合XDomainRequest对象(IE10以下)是否存在,就可以兼顾所有浏览器了。
function creaeCORSRequest(method,url){ var xhr=new XMLHttpRequest(); if("withCredentials" in xhr){ xhr.open(method,url,true); }else if(typeof XDomainRequest!="undefined"){ xhr=new XDomainRequest(); xhr.open(method,url);//没有第三个参数 }else{ xhr=null; }return xhr; }var request=createCORSRequest("get","http://www.abc.com");if(request){ request.=function(){ console.log(request.responseText);//处理响应信息 }; request.=function(){ //处理错误 } request.send(); }
和分别代替onreadystatechange的检测错误和检测成功
JSONP
1、什么是JSONP
JSONP是JSON with padding的缩写,通过创建script元素来使用,让其src属性为一个跨域的URL,在URL后面添加响应后的回调函数,响应的数据以参数的形式传入回调函数。
2、如何使用jsonp跨域
//res响应数据function handleResponse(res){ console.log(res) }var script=document.createElement('script')//handleResponse回调函数script.src="http://freegeoip.net/json/?callback=handleResponse";document.body.insertBefore(script,document.body.firstChild)
3、JSONP优缺点
优点:能直接访问响应文本,支持在浏览器与服务器之间双向通信。
缺点:其他域可能有恶意脚本,jsonp的请求是否失败不容易无额定
4、cors与jsonp比较
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
图像Ping
1、什么是图像Ping
图像Ping是与服务器进行简单、单向的跨域通信的一种方式,请求的数据通过查询字符串的形式发送,响应可以是任意内容,但通常是像素图或者204(204 No Content
成功状态响应码表示目前请求成功,但客户端不需要更新其现有页面。204 响应默认是可以被缓存的。在响应中需要包含头信息 ETag
)。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,就能知道响应是什么时候接收到的。
2、实现样例
var img=new Image(); img.=img.=function(){ console.log('done') } img.src="http://www.example.com/test?name=Nicholas";
修改document.domain跨子域通信
1、适用情况
当某个页面中包含其他子域的框架或者内嵌框架时,可以通过将每个页面的document.domain
设置为相同的值来通信,设置的域名应该是他们的父域。浏览器对domain有一个限制,如果一开始document.domain是松散的,那么就不能再紧绷。例如
//假设页面来自于p2p.wrox.com 域document.domain = "wrox.com"; //松散的(成功)document.domain = "p2p.wrox.com"; //紧绷的(出错!)
使用栗子
//html,在http://example.com/a.html中嵌入iframe<iframe src="http://example.com/b.htm" id=''iframe" = "test()"></iframe> //http://example.com/a.html中js document.domain =‘example.com’ function test(){ alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象 } //http://example.com/b.html中js document.domain =‘example.com’
跨文档消息传递
1、是什么
跨文档消息传送(cross-document messaging),有时候简称为XDM,指来自不同域的页面间传递消息。一般向包含在当前页面中的iframe元素或者由当前页面弹出的窗口传递。例如:比如www.baidu.com
域的A页面通过iframe嵌入了一个google.com
域的B页面。XDM核心方法是postMessage(message,tourl)
方法,两个参数,第一个是消息,第二个是把消息传给谁。接收到XDM消息后,触发window
的onmessage
事件。
2、如何用
A页面通过postMessage方法发送消息:
//支持XDM的浏览器也支持iframe的contentWindow属性window. = function() { var ifr = document.getElementById('ifr'); var targetOrigin = "http://www.google.com"; ifr.contentWindow.postMessage('hello world!', targetOrigin); };
B页面通过message事件监听并接受消息:
window.onmessage = function (event) { var data = event.data;//消息 var origin = event.origin;//消息来源地址 var source = event.source;//源Window对象 if(origin=="http://www.baidu.com"){ console.log(data);//hello world! } };
Web Socket
Web Socket目标是在一个单独的持久连接上提供全双工、双向通信(非同源),使用自定义协议,未加密:ws://
,加密:wss://
。缺点,使用自定义协议,时间较长。
1、实例化
var socket=new WebSocket("ws://www.demo.com/server.php")
实例化后,浏览器会马上尝试创建链接。WebSocket也有readyState属性
0:正在建立连接
1:已经建立连接
2:正在关闭链接
3:已经关闭链接
2、发送和接收数据
发送
只能发送纯文本数据
,对象等数据结构需要序列化为JSON字符串。
- 纯文本socket.send('hello')
- 对象
var obj={ name:'ok'} socket.send(JSON.stringify(message))
接受数据
socket.onmessage=function(event){ console.log(event.data)//数据在event.data中}
3、其他事件,在连接的不同生命周期触发
open:成功建立连接
error:发生错误时,连接不能持续
close:连接关闭
必须使用DMO0级定义事件处理程序
4、使用样例
var socket=new WebSocket("wss://www.demo.html/server.js") socket.onopen=function(){ console.log("建立连接") socket.send('hello') } socket.onmessage=function(event){ console.log(event.data) } socket.=function(){ console.log("错误") } socket.onclose=function(){ console.log("关闭链接") }
Hash跨域
使用场景,当页面A通过iframe或者frame嵌入了跨域的页面B,我们可以跨域改变B页面的hash。改变hash不会刷新页面,不会请服务器发送请求。
//A中的代码var B=document.getElementByTagName('iframe')[0] B.src=B.src+'#'+'data'//B中的代码window.onhashchange=function(e){ var data=window.location.hash }
安全
一、CSRF
概念
CSRF(Cross-Site Request Forgery)跨站请求伪造
攻击原理
CSRF防御
(1)验证 HTTP Referer 字段
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址
(2)在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在
HTTP 请求中以参数
的形式加入一个随机产生
的token
,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
(3)每次请求都要附带经过相应算法计算得到的验证码
二、XSS
概念
跨站脚本攻击(Cross Site Scripting)。
原理
往页面中 注入恶意script代码
,当代码被浏览器解析执行
时便达到攻击的目的。
防御
不能原样的将用户输入的数据直接存到服务器,需要对数据进行一些处理。
过滤危险的DOM节点。如具有执行脚本能力的script, 具有显示广告和色情图片的img, 具有改变样式的link, style, 具有内嵌页面的iframe, frame等元素节点。
过滤危险的属性节点。如事件, style, src, href等
对cookie设置httpOnly。
参考资料
JavaScript高级程序设计第三版
作者:fenerchen
链接:https://www.jianshu.com/p/50b2a9fd9f78