浏览器安全的基石是同源政策(same-origin policy)。
一、概述
1.1含义和目的
1995年,同源政策由Netscape公司引入浏览器。目前,所有浏览器都实行这个政策。
最初的含义是指,A网页设置的cookie,B网页不能打开,除非这两个网页同源
同源指的是“三个相同”
- 协议相同
- 域名相同
- 端口相同
同源政策的目的,是为了保证用户的信息安全,防止恶意的网站窃取数据。
如果cookie包含隐私,这些信息就会泄漏。
cookie往往用来保存用户的登录状态,如果用户没有退出登陆,其他网站就可以冒充用户。
因为浏览器同时还规定,提交表单不受同源政策的限制。
“同源政策”是必须的,否则cookie可以共享,互联网就毫无安全可言了。
1.2限制范围
目前,如果非同源,共有三种行为受到限制
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM无法获得
- AJAX请求不能发送
二、同源规避方法
同源政策规定,AJAX请求只能发给同源的网址,否则就报错
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),由三种方法规避这个限制
- JSONP
- WebSocket
- CORS
2.1 JSONP
可跨域HTML元素
用form可以发送post请求,但是会刷新页面或新开页面。可跨域但拿不到响应
用a可以发GET请求,但是也会刷新页面或新开页面。可跨域
用img可以发GET请求,不用放在body标签里,但是只能以css、favicon的形式展示。可跨域
button.addEventListener('click', (e)=>{
let image = document.createElement('img')
image.src = '/pay'
image.onload = function(){
alert('成功')
}
image.onerror = function(){ // 状态码大于等于 400 则表示失败
alert('失败')
}
})
用script可以发GET请求,要放在body标签里但是只能以脚本的形式运行,完成后清楚script标签,可跨越
button.addEventListener('click', (e)=>{
let script = document.createElement('script')
script.src = '/pay'
document.body.appendChild(script)
script.onload = function(e){ // 状态码是 200~299 则表示成功
e.currentTarget.remove()
}
script.onerror = function(e){ // 状态码大于等于 400 则表示失败
e.currentTarget.remove()
}
//后端代码
...
if (path === '/pay'){
let amount = fs.readFileSync('./db', 'utf8')
amount -= 1
fs.writeFileSync('./db', amount)
response.setHeader('Content-Type', 'application/javascript')
response.write('amount.innerText = ' + amount)
response.end()
}
...
JSONP是服务器与客服端跨源通信的常用方法,最大特点就是简单实用,老式浏览器全部支持,服务器改造非常小。
JSOP原理(发源自SRJ-Server Rendered JavaScript)
网页通过添加一个<script>
元素,向服务器请求JSON数据
这种做法不受同源政策限制,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
-
动态添加
<script>
元素。 -
向服务器发送请求,该请求的查询字符串有一个
callback
参数,用来制定回调函数的名字,这对于JSONP是必须的window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); }
-
服务器收到这个请求后,会将数据放在回调函数的参数位置返回。
foo.call(undefined,{
"ip": "8.8.8.8"
});
-
<script>
元素请求的脚本直接作为代码运行,只要浏览器定义了foo
函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串。避免了JSON.parse
的使用
JSON+padding = JSONP
JSONP的具体实现
请求方:frank.com的前端页面(浏览器)
响应方:jack,com的后端(服务器)
-
请求方动态创建script标签,src指向响应方,同时传一个一查询参数?callback=xxx
-
响应方根据请求方的查询参数?callback=xxx,定制callback函数的名字,这对JSONP是必须的
-
xxx.call('undefined', {请求的数据})
-
xxx.({请求的数据})数据会放在回调函数的参数位置返回
响应方构造如上响应。
-
浏览器接收到响应后,就会执行xxx.call('undefined','请求的数据')
-
请求方就获得了想要请求的数据。
注意:后台都是返回一个符合对象规则的字符串,后台是没有办法返回一个对象给前端的。
问题:为什么JSONP不支持POST?
- 因为JSONP是通过动态创建script标签实现对跨源的跨源的服务器进行通信的。
- 动态创建的script标签只能用GET,不支持POST。
JSONP的具体实现
button.addEventListener('click',(e)=>{
let script = document.createElement('script')
let fucntionName = 'dong' + parseInt(Math.random()*100000,10) // 使用随机数做函数名避免污染全局变量
window[functionName] = function (result){
if(result === 'success'){
amount.innerText = amount.innerText - 1//string转化为number
}
}
script.src = '/pay?callback=' + functionName
document.body.appendChlid(script)
script.onload = function(e){ // 状态码是 200~299 则表示成功
e.currentTarget.remove()
delete window[functionName]
}
script.onerror = function(e){ // 状态码大于400表示失败
e.currentTarget.remove()
delete window[functionName] // 请求完了就干掉这个随机函数
}
})
//后端代码
if(path === '/pay'){r
fs.writeFlieSync('./db','utf8')
let callbackName = query.callback
response.setHeader('Content-type','application/javascript')
response.write(`${callbackName}.call(undefined,’success‘)
response.end()
}
使用jQuery实现JSONP
$.ajax({//和ajax无关系只是JSONP
url: "http://dong.com:80/pay",
dataType: "jsonp",
success: function(res) {
if (response === "success"){
amount.innerText = amount.innerText - 1
}
}
})
2.2 WebSocket
websocket是一种通信协议,实用ws:// (非加密) wss:// (加密)作为协议前缀
该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信
2.3 CORS
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET
请求,CORS允许任何类型的请求。
aButton.addEventListener('click',(e)=> { //dongdong前端
let request = new XMLHttpRequest
request.open('get', 'http://dongdong.com:8001')//配置。open(method, url, asyns, user, password)后3个参数可选
request.onreadystatechange = () => {
if(request.readyState === 4){
if(request.status >=200 && request.status < 300){
let string = request.responseText
// 把符合JSON语法的字符串转换为JavaScript对应的值
let object = window.JSON.parse(string)
console.log(object.note)
}else if(request.status >= 400){
console.log('请求失败')
}
}
}
request.send()
})
// 后端代码 jack
}else if(path === '/xxx'){
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.setHeader('Access-Control-Allow-Origin', 'http://dongdong.com:8001')//CORS
response.setHeader('Access-Control-Allow-Origin', 'http://123456.com:8001')//CORS 允许多个域名跨源访问服务器。
response.wtite(`{
"note": {
"to": "dongdong"
"from": "小瓜太郎"
"content": "hi"
}
}`)
response.end()
}
三、AJAX
AJAX = async + javascript + and + xml(现在为JSON)
在AJAX出现以前(SRJ以前只能通过特定的HTML标签发起有限制的HTTP求情)
用form可以发送post请求,但是会刷新页面或新开页面。
用a可以发GET请求,但是也会刷新页面或新开页面。
用img可以发GET请求,不用放在body标签里,但是只能以css、favicon的形式展示。
用script可以发GET请求,要放在body标签里但是只能以脚本的形式运行,完成后清楚script标签
有什么方式可以实现以下要求:
- GET 、POST、PUT、DELETE请求都行。
- 想以什么形式展示就以什么形式展示。
微软的突破
以前只能通过特定的HTML标签发起有限制的HTTP求情
IE5开始在js中引入activeX对象(API),让JS可以直接发起HTTP请求
随后Mozilla、Safari、Opera跟进,取名XMLHttpRequest纳入W3C规范。
AJAX
- 使用XMLHttpRequest发送请求
- 服务器返回XML格式的字符串
- JS解析XML,并更新局部页面
使用XMLHttpRequest
aButton.addEventListener('click',(e)=> {
let request = new XMLHttpRequest
request.open('get','/xxx')//配置。open(method, url, asyns, user, password)后3个参数可选
request.onreadystatechange = () => {
if(request.readyState === 4){ //0unset 1opened 2headers_received(send) 3loading 4done
if(request.status >=200 && request.status < 300){
console.log('请求成功')
console.log(typeof request.responseText)
console.log(request.responseText)
let string = request.responseText
// 把符合JSON语法的字符串转换为JavaScript对应的值
let object = window.JSON.parse(string)
console.log(typeof object)
console.log(object)
console.log(object.note)
}else if(request.status >= 400){
console.log('请求失败')
}
}
}
request.send()
})
// 后端代码
}else if(path === '/xxx'){
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.setHeader('Access-Control-Allow-Origin', 'http://frank.com:8001')
response.wtite(`{
"note": {
"to": "dongdong"
"from": "小瓜太郎"
"content": "hi"
}
}`)
response.end()
}
四、封装AJAX、Promise
AJAX的所有功能
- 客户端的JS发起请求(浏览器上的)
- 服务器端的JS发送响应(Node.js上的
- JS可以设置任意请求header
第一部分 request.open('get','/xxx')
第二部分 request.setRequestHeader('Content-Type','x-www-form-urlencoded')
第四部分 request.send('a=1&b=2')//body
)
- JS可以获取来自服务器端的任意响应header
第一部分 request.status/request.statusText//200/OK
第二部分 request.getResponseHeader()/request.getAllResponseHeaders
第四部分 request.reponseText
window.jQuery = function(nodeOrSelector){
let nodes = {}
nodes.addClass = function(){}
nodes.html = function(){}
return nodes
}
window.$ = window.jQuery
window.Promise = function(fn){ //Promise的简单原理
// ...
return {
then: function(){}
}
}
window.jQuery.ajax = function({url,method,body,headers}){// es6解构赋值
return new Promise(function(resolve,reject){
let request = new XMLHttpRequest()
request.open(method,url)
for(let key in headers){
let value = headers[key]
request.setRequestHeader(key,value)
}
request.onreadystatechange = (e) => {
if(request.readyState === 4){
if(request.status >= 200 && request.status < 300){
resolve.call(undefined,request.responseText)
}else if (request.status >= 400){
reject.call(nudefined,request)
}
}
}
request.send(body) //若是POST
})
}
aButton.addEventListener('click', (e) => {
let promise1 = window.jQuery.ajax({
url:'/xxx',
method:'get',
headers: {
'content-type':'application/x-www-form-urlencoded',
'frank': '18'
}
})
promise1.then( //在不知道回调函数名字的情况下,拿到绑定的参数
(text) => {console.log(text)},
(request) => {console.log(request)}
)
})
// callback call a function back
回调函数
callback = call a function back
回调的问题
每个程序员的回调名不一样。
Promise解决了这个问题
function xxx(){
return new Promise((f1, f2) => {
doSomething()
setTimeout(()=>{
// 成功就调用 f1,失败就调用 f2
},3000)
})
}
xxx().then(success, fail)
// 链式操作
xxx().then(success, fail).then(success, fail)
??? 关于then return
网友评论