这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
一、同源策略
1.1 什么是同源策略?
同源策略(Same-origin Policy):为了保证浏览器的信息安全,浏览器采用同源策略,保证当前源中的资源只能在当前的源中使用;其他源如果需要使用当前源资源,需要特殊技术,这种A源访问B源的资源的通信称为跨域;
1.2 示例:
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com/', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('xxx')
}
};
xhr.send();
- 以上请求会报错:
Access to XMLHttpRequest at 'https://www.baidu.com/' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
当出现以上错误时说明你正在进行一个跨域的操作;
1.3 同源策略的要求:
同源策略要求通信的两个源的协议、域名、端口号要相同,如果三者中任意一个不同就是不满足同源策略;不满足同源策略的通信就是跨域;
1.4 常用的跨域解决方案:
- JSONP
- 服务端转发,因为同源策略只在客户端存在,在服务端是不存在的;所以可以由服务端转发请求;
- nginx转发,nginx是服务器应用程序,它可以接受客户端的请求,然后根据规则可以配置自动转发;
- CORS: Cross-Origin-Resource-Sharing: 需要目标域设置 Access-Control-Allow-Origin 头信息;
二、JSONP
JSONP是一种常用的解决跨域的方式;
2.1 原理:
利用script的src属性是不受同源策略约束的,可以访问不同服务器或者端口号下的数据
- 提前声明一个叫做fn的函数,给fn设置一个形参;
- 在页面给script的src的指向的路径拼接一个callback属性,callback=fn;当浏览器解析到这个script标签时,会向src指向的路径发起http请求;
- 服务器收到这个请求后,会返回一个fn(这里面是服务器返回的数据)
- fn({xxx}) 这个是让fn执行,小括号里面就是服务器发送给我们的数据
2.2 示例:
JS代码:
function fn(data) {
console.log(data);
}
HTML代码
<script src="http://matchweb.sports.qq.com/kbs/calendar?columnId=100000&callback=fn"></script>
三、CORS
3.1 定义
CORS: CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用。
使用 CORS 来解决跨域问题时,通过约定 HTTP 响应头信息告知客户端目前的跨域允许情况,常见有以下这些头信息:
Access-Control-Allow-Headers允许携带的请求头,*表示任意;Access-Control-Allow-Methods跨域访问时使用的方法,常见的方法GET,PUT,POST,DELETE,OPTIONS;Access-Control-Allow-Max-Age设置一个跨域配置信息的缓存期限,比如10s, 标识 10s 内不要再发 OPTIONS 请求询问跨域配置情况;Access-Control-Allow-Credentials, 设置是否允许携带cookie,true是否允许携带cookie来,如果不设置这个值,目标源也无法设置客户端的cookie
3.2 一个示例
以下是一个 Nodejs 开发的简易 server 代码:
const http = require('http');
const fs = require('fs');
const path = require('path')
const url = require('url');
http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true)
let method = req.method.toUpperCase()
res.setHeader('Access-Control-Allow-Headers', 'token');
res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Max-Age', '10');
res.setHeader('Access-Control-Allow-Credentials', true);
// res.setHeader()
if (method === 'OPTIONS') {
return res.end();
}
if (pathname === '/user') {
switch (method) {
case 'GET':
res.end(JSON.stringify({ name: 'ok' }))
break
case 'POST':
let arr = []
req.on('data', chunk => arr.push(chunk))
req.on('end', () => {
let r = Buffer.concat(arr).toString()
res.setHeader('Set-Cookie', 'a=333333')
res.end(JSON.stringify({b: 'post'}))
})
}
return
}
let filepath = path.join(__dirname, pathname)
fs.stat(filepath, (err, statObj) => {
if (err) {
res.statusCode = 404;
return res.end('not found')
}
if (statObj.isFile()) {
return fs.createReadStream(filepath).pipe(res)
}
res.statusCode = 404;
res.end('not found')
})
}).listen(8081, () => {
console.log('ok 8081')
})
3.3 注意事项
3.3.1 form 表单
form 表单的提交是不存在跨域问题的,form 的提交可以提交到任意的源;
<form action="http://www.unkown.com/some/path/app.php" method="get">
<p>First name: <input type="text" name="f_name" /></p>
<p>Last name: <input type="text" name="l_name" /></p>
<input type="submit" value="Submit" />
</form>
3.3.2 复杂请求和 OPTIONS
当浏览器识别到这是一个跨域行为时,如果该请求是个复杂请求则会发起一次 OPTIONS 请求,向服务器问询是否允许跨域等相关配置;
GET/POST 都是简单请求,如果请求中包含了自定义的请求头将变为复杂请求,这个时候浏览器自动发送 OPTIONS 请求,再得到服务端正确的 CORS 响应后才会发起真正的请求。如果服务器 OPTIONS 请求没有返回,或者返回不正确,则不回复发送真正请求并抛出同源策略限制的错误;