同源政策
同源政策(same-origin policy),作为浏览器安全的基石,可以保证用户信息的安全,防止恶意的网站窃取数据,
1.何为同源
- 协议相同
- 域名相同
- 端口相同
2.非同源受限制的行为
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
注意: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域
跨域
当浏览器在一个页面上去请求另一个域名的资源,这两个域是不同源的,就需要跨域的操作;
跨域的方式:JSONP、CORS、降域 和 postMessage
1.JSONP(JSON with padding)
通过JSONP实现跨域的基本思想是:HTML中的<script>标签可以下载其它域下的js文件,这种做法是不受同源政策的限制的。因此,可以利用这个特性从不同源的域下获取数据。下面通过一个例子讲解实现方式。
- HTML的内容
<body>
<ul></ul>
<button>展示数据</button>
</body>
<script>
function $(selector) {
return document.querySelector(selector)
}
//这里给button绑定事件,点击button时,在<head>里添加一个<script>标签,指定src
$('button').onclick = function(){
var script = document.createElement('script')
script.src = 'http://127.0.0.1:8080/getData?callback=showData';
document.head.appendChild(script)
}
//这里声明showData函数
function showData(data) {
var html = ''
for(var i=0; i<data.length;i++) {
html += '<li>' + data[i] + '</li>'
}
$('ul').innerHTML = html
}
</script>
- 搭建一个服务器
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
var server = http.createServer(function(req,res) {
routePath(req,res)
})
function routePath(req,res) {
var pathObj = url.parse(req.url,true)
switch(pathObj.pathname) {
case '/getData':
var data = [
'数据1',
'数据2',
'数据3'
]
//<script>标签加载的是JS文件,服务器返回的数据是JSON格式的数据,所以这里对返回的数据做处理
if(pathObj.query.callback) {
res.end(pathObj.query.callback + '(' + JSON.stringify(data) + ')')
//最终得到的是执行showData函数,参数是原本要返回的数据,即上面的data
}else{
res.end(JSON.stringify(data))
}
break;
//这里是没有路由时,处理静态目录
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}
server.listen(8080)
我们打开html的url是http://localhost:8080/index.html
,而我们在html中添加的<script>的标签的src是http://127.0.0.1:8080/getData?callback=showData
,根据同源政策,这两个是不同源的。但是,<script>标签加载数据是不受同源政策限制的。
正常的用ajax发送请求
//对js做个修改
$('button').onclick = function(){
var xhr = new XMLHttpRequest()
xhr.open('GET','http://127.0.0.1:8080/getData?callback=showData')
xhr.onload = function(){
console.log(xhr.responseText)
}
xhr.send()
}
点击按钮,发送请求后,查看控制台,发现浏览器拒绝接受响应,因为发送请求的url和当前页面的url是不同源的
2.CORS
CORS,全称是“跨域资源共享”(Cross-origin resource sharing),它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。
基本原理是:当使用 XMLHttpRequest 发送请求时,浏览器如果发现该请求不符合同源政策,会给该请求加一个请求头:Origin,服务器进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器接收响应时,判断响应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。
一个例子:
- HTML的内容
<body>
<ul></ul>
<button>展示数据</button>
</body>
<script>
function $(selector) {
return document.querySelector(selector)
}
function showData(data) {
var html = ''
for(var i=0; i<data.length;i++) {
html += '<li>' + data[i] + '</li>'
}
$('ul').innerHTML = html
}
$('button').onclick = function(){
var xhr = new XMLHttpRequest()
xhr.open('GET','http://127.0.0.1:8080/getData')
xhr.onload = function(){
showData(JSON.parse(xhr.responseText))
}
xhr.send()
}
</script>
- 搭建服务器
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
var server = http.createServer(function(req,res) {
routePath(req,res)
})
function routePath(req,res) {
var pathObj = url.parse(req.url,true)
switch(pathObj.pathname) {
case '/getData':
var data = [
'数据1',
'数据2',
'数据3'
]
//这里是关键的一步,给响应头添加Access-Control-Allow-Origin,后面的值表示接受该域名的请求
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
res.end(JSON.stringify(data))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}
server.listen(8080)
我们点击按钮,发送请求,同时查看请求头和响应头的内容。
我们可以看到,Origin
的值和Access-Control-Allow-Origin
的值一样,最终的请求也是有效的,浏览器页接受了响应。
与JSONP的比较
CORS 与 JSONP 的使用目的相同,但是比 JSONP 更强大。JSONP 只支持GET请求,CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
3.降域
降域是通过设置 document.domain 的值,解决跨域问题;
降域的适用范围较窄,一般是页面中有iframe元素,需要获取iframe中的页面的数据时使用降域来解决跨域的问题;
基本原理:例如
A页面域名为:a.com;ifram中引入B页面
B页面域名为:b.com;
在A、B页面写入document.domain = 'localhost:8080.com';
(这里的值是主域名)
这样的话就可以在A页面中获取B页面的DOM数据,操作B页面中的元素;
4.postMessage(不常用)
这个方法是利用HTML5中引入的一个API:window.postMessage() ;
它的原理就是,比如一个父窗口,向一个子窗口(比如ifram)发送消息,子窗口可以通过message
事件,监听到父窗口发送过来的消息,并作出相应的操作,同理,子窗口也能向父窗口发送消息。通过这种操作也能实现跨域。