同源策略
1、同源定义
如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。
例如,下表给出了相对于 http://www.test.com/index.html (默认端口号80)页面的同源检测:
URL | 是否同源 | 原因 |
---|---|---|
http://www.test.com/other.html | 是 | 同源(协议、域名、端口相同) |
https://www.test.com/about.html | 否 | 协议不同(http 与 https) |
http://blog.test.com/movie.html | 否 | 域名不同(www.test.com 与 blog.test.com) |
http://www.test.com:7001/home.html | 否 | 端口不同(默认的 80 端口与 7001 端口) |
http://www.test.com:80/main.html | 是 | 同源(协议、域名、端口相同) |
2、同源策略
同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。
**MDN 官方给定的概念:**同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
**通俗的理解:**浏览器规定,A 网站的 JavaScript,不允许和非同源的网站 C 之间,进行资源的交互,例如:
①无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
②无法接触非同源网页的 DOM
③无法向非同源地址发送 Ajax 请求
3、跨域
同源指的是两个 URL 的协议、域名、端口一致,反之,则是跨域。
出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。
网页:http://www.test.com/index.html
接口:http://www.api.com/userlist
3.1浏览器对跨域请求的拦截
**注意:**浏览器允许发起跨域请求,但是,跨域请求回来的数据,会被浏览器拦截,无法被页面获取到!
3.2解决方案
普通的AJAX请求(非跨域的情况)是默认携带cookie的,但是在跨域时则是不携带cookie的。跨域时携带cookie的方法有三种:
Nginx反向代理
JSONP
CORS
对Cookie跨域的理解
假设服务端的域名是a.com,发送跨域请求的前端的域名是b.com,那么在b.com想a.com发送跨域请求时,是可以携带cookie的,但是这个cookie必须是域名为a.com下的cookie
也就是说,b.com的前端发送的跨域请求携带的cookie,是目标页面所在域的cookie。
所以带cookie跨域的前提是目标页面的cookie在本机存在,跨域要携带的cookie必须是目标页面所在域的cookie
理解误区:
1.b.com向a.com发送跨域请求,可以把b.com域名下的cookie带上。如上面所说的,这是行不通的
2.b.com通过JS在本机生成一个域名a.com的Cookie,或者a.com的服务端在发送响应时setCookie的domain为b.com。这两种做法都是行不通的,因为设置cookie的domain可以设置为父域名和自身,但是不能设置其他域名和子域名,否则cookie设置不会成功。
domain假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。与之前的规范不同的是,域名之前的点号会被忽略。假如指定了域名,那么相当于各个子域名也包含在内了。
服务端是无法跨域设置cookie的(set-cookie),只能设置自身域名或者父域名的Cookie
前端是可以带cookie跨域的,前提是cookie是目标服务器所在域的cookie
Nginx反向代理
通过Nginx反向代理来解决cookie跨域问题可以携带cookie。
反向代理解决跨域问题的方案依赖同源的服务端对请求做一个转发处理,将请求从跨域请求 转换成同源请求。
涉及到的端:反向代理只需要服务端/后端支持,几乎不涉及前端改动,只用切换接口即可。
具体实现方式:
反向代理的实现方式为在页面同域下配置一套反向代理服务,页面请求同域的服务端,服务 端请求上游的实际的服务端,之后将结果返回给前端。
(代理转发(只有浏览器会出现跨域问题,服务器与服务器之间不会出现跨域问题))
JOSNP
JSONP 是一个相对古老的跨域解决方案。主要是利用了浏览器加载 JavaScript 资源文件时 不受同源策略的限制而实现跨域获取数据。
涉及到的端:JSONP 需要服务端和前端配合实现。
JSONP 的缺陷主要在于只能通过 script 或 img 等标签的 src 属性发送请求,即只能发送 GET 请求。
前端实现
在 script 标签的 src 上,我们指定好需要服务器进行填充的回调函数名 setUser,并带上用户 id。
<script src="http://b.com:4000/user?id=2&callback=setUser"></script>
上面这种直接这样写到 HTML 里不太灵活,我们改写成下面这样。
let user = null;
function setUser(user) {
// 保存用户信息
window.user = user;
// 输出到页面上,看看效果。
document.body.append(
JSON.stringify(user);
);
}
function getUserById(id) {
const script = document.createElement('script');
script.src = `http://b.com:4000/user?id=${id}&callback=setUser`;
document.body.appendChild(script);
}
document.querySelector('button').onclick = function() {
getUserById(2);
}
后端实现
然后是服务端的处理,这里使用Nodejs 的 Express 框架。
const app = express();
// ...
const map = {
1: { name: 'sweet_xia' },
2: { name: '前端' }
};
// 请求url:/user?id=2&callback=setUser
app.get('/user', (req, res, next) => {
const { id, callback } = req.query;
res.send(`${callback}(${JSON.stringify(map[id])})`);
});
// ...
服务端从 url 的请求字段中提取出 id ,找到对应的用户信息(通常为 JSON 的形式),配合要填充的回调函数 setUser,组装成字符串 setUser({“name”:“前端”})
这个内容会作为脚本内容返回给前端(前端获得的数据),前端运行这个脚本后,就会执行全局作用域下的 setUser 函数,这个函数还会拿到用户信息,将其保存下来。
非常用方式
postMessage
即在两个 origin 下分别部署一套页面 A 与 B,A 页面通过 iframe 加载 B 页面并 监听消息,B 页面发送消息。
window.name
主要是利用 window.name 页面跳转不改变的特性实现跨域,即 iframe 加载一个 跨域页面,设置 window.name ,跳转到同域页面,可以通过 $(‘iframe’).contentWindow.name 拿到跨域页面的数据。 document.domain 可将相同一级域名下的子域名页面的
document.domain
设置为一级域名实现跨 域。 可将同域不同端口的 document.domain 设置为同域名实现跨域(端口被置为 null)。
总结:
JSONP 是一种解决跨域的方案,但一般比较少用到。
因为 JSONP 并不是标准,也不安全,服务端代码没写好会有代码注入的风险,且无法防范跨站请求伪造(CSRF)攻击。
CORS
CORS 是目前最为广泛的解决跨域问题的方案。方案依赖服务端/后端在响应头中添加 Access-Control-Allow-* 头,告知浏览器端通过此请求。
涉及到的端:CORS 只需要服务端/后端支持即可,不涉及前端改动。
具体实现方式
CORS 将请求分为简单请求(Simple Requests) 和需预检请求(Preflighted requests) ,不同场景有不同的行为:
不会触发预检请求的称为简单请求。当请求满足以下条件时就是一个简单请求:
请求方法: GET 、 HEAD 、 POST。
请求头: Accept 、 Accept-Language 、 Content-Language 、 Content-Type。 Content-Type 仅支持: application/x-www-form-urlencoded、multipart/form-data 、 text/plain。
需预检请求
触发预检请求的三类条件:
默认情况下,跨域请求只支持GET,HEAD,POST方法,如果不是这三个请求方法(比如:PUT、DELETE、CONNECT、OPTIONS、TRACE和PATCH),那么将触发预检请求
默认情况下,浏览器跨域请求时,会自动添加的请求头(HOST,Referer,Connection、Accept、User-Agent,Accept-Languange,Accept-Encoding,Accept-Charset和Content-Type),这些请求中还有其他请求头时,那么将触发预检请求。
如1、2所说的情况排除在外的条件下,跨域请求是,浏览器支持的Content-Type值为application/x-www-form-urlencoded,multipart/form-data和text/plain。如果是其他数据类型(如application/json,text/xml…),那么将触发预检请求。
当一个请求不满足以上简单请求的条件时,浏览器会自动向服务端发送一个 OPTIONS 请 求,通过服务端返回的 Access-Control-Allow-* 判定请求是否被允许。
CORS 引入了以下几个以 Access-Control-Allow-* 开头:
Access-Control-Allow-Origin 表示允许的来源
Access-Control-Allow-Methods 表示允许的请求方法
Access-Control-Allow-Headers 表示允许的请求头
Access-Control-Allow-Credentials 表示允许携带认证信息
当请求符合响应头的这些条件时,浏览器才会发送并响应正式的请求。