1. 前言
由于http协议的无状态性
。每一次的http请求不会记住和保存任何会话信息,比如上一次的请求发送者和这一次的发送者是不是同一个人?每次请求都是全新和独立的。随着交互式Web应用的兴起, 像在线购物网站,需要登录的网站等,马上面临一个问题,就是要管理回话,记住那些人登录过系统,哪些人往自己的购物车中放商品,也就是说我必须把每个人区分开。
2. Cookie
Cookie是浏览器实现的一种数据存储技术。一般由服务器生成,发送给浏览器(客户端也可进行cookie设置)进行存储,下一次请求同一网站时会把该cookie发送给服务器。
2.1 Cookie的特点
1:cookie存储在客户端(浏览器),发送请求时自动携带放在请求头中。
2:cookie只能以文本的方式保存字符串类型的数据。
3:单个cookie保存的数据不能超过4KB。
4:cookie的安全性不高,别人可以分析存放在本地的cookie并进行cookie欺骗。
5:cookie默认不可跨域,可通过特殊的操作如设置withCredentials属性为true
实现跨域。(参考链接:跨域的介绍)。
2.2 Cookie的属性
cookie的每个属性都通过name=value
的形式进行设置,属性之间通过分号加空格
进行分割。我们可以根据需求去自定义cookie中需要传递的数据,以下是cookied的一些内置原生的可设置的基本属性:
属性名称 | 说明 |
---|---|
domain | 指定cookie所属域名,默认当前域名 |
path | 指定cookie在哪个域名路由下生效,默认为/ |
maxAge | 指定cookie的有效时长,单位为秒。当值小于0时表示当前会话关闭后即失效,为-1时表示删除该cookie。默认为-1。 |
expires | 指定cookie的有效截至日期(现已被maxAge取代)。 |
secure | 指定cookie是否仅被使用安全协议传输。当该值为true,则cookie仅在https中生效,http无效。默认为false |
httpOnly | 若该属性有值,则客户端无法通过JS脚本去获取cookie信息,但仍可以在浏览器的Application工具中手动修改cookie。具备一定程度的安全性。 |
2.3 Cookie的设置
1:客户端设置
可通过JavaScript脚本来获取和设置cookie。客户端可以设置expires
, domain
, path
, secure
(只有在https协议的网页中, 客户端设置secure类型cookie才能生效), 但无法设置httpOnly
选项。
示例:
document.cookie = "name=xiaoming; age=12 "
2:服务端设置
浏览器会发送HTTP请求到服务端后,服务器在进行响应到客户端时可以在响应头中设置Set-Cookie
属性从而保存cookie在客户端中。注意:
- 一个
Set-Cookie
属性只能设置一个cookie, 当你想设置多个, 需要添加同样多的Set-Cookie
。 - 服务端可以设置cookie的所有选项: expires, domain, path, secure, HttpOnly。
2.4 在iframe中cookie无效的解决方案
参考文章:https://juejin.cn/post/6935683384710529055
3. Session
session 是另一种记录服务器和客户端会话状态的机制,并且session 是基于 cookie 实现的。服务器要知道当前请求发给自己的是谁,为了做这种区分,服务器就是要给每个客户端分配不同的"身份标识",然后客户端每次向服务器发请求的时候,都带上这个“身份标识”。
session 认证流程:
用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session。请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器。浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此SessionID 属于哪个域名。
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
3.1 Session的特点
1:session 是基于 cookie 实现,cookie失效或删除则session也无法获取。
2:Session 是存储在服务器端,所以安全性比cookie高。
3:Session 可以存任意数据类型。
4: Session的默认生效时间是30分钟。只要在生效时间内,即使该session值已被修改,依然可通过旧有Cookie访问到旧有Session值。
5:Session 可存储数据的容量远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
4. Token
虽然Session具备一定的安全性,但是它的问题在于扩展性不好。比如涉及到服务器集群的场景,要求每台服务器都能够读取session。这种场景一般解决方案是session共享,经典应用需求是A和B两个关联网站间的单点登录,但是这种方案一般工程量较大。于是便出现了另一种服务器无需保存session的技术方案:Token
。
验证流程:
1:客户端使用用户名跟密码请求登录。
2:服务端收到请求,去验证用户名与密码。
3:验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端。
4:客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里。
5:客户端每次向服务端请求资源的时候需要带着服务端签发的 token。
6:服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据。
4.1 Token的特点
1:每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里。
2:token签发后存储在客户端,不占用服务器资源,可减轻服务器压力。
3:使用token无需担心跨域问题,可自由使用。
5. JWT
JWT(Json Web Token):Token技术的一种现有标准,也是目前最流行的跨域认证解决方案。JWT主要由Header(头部),Payload(负载),Signature(签名)三部分组成。如下图示例:
JWT与Token的认证过程基本相似,主要的不同点在于:
Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
JWT:将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。
5.1 JWT的使用方式
jwt一般可暂时存储在localstorage或cookie中
方便获取
1: 将JWT放在Cookie 里面自动发送,缺点是默认不可跨域。
2: 将JWT放在HTTP 请求头信息的 Authorization 字段里(主流做法)。
如下所示:
Authorization: Bearer <token>
3:JWT就放在POST请求的数据体里。
4:JWT放在GET请求的参数里(即URL里)
6. 注意事项
1:移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token
2:cookie由于安全性不高,应避免存储一些比较重要的敏感数据。
3:使用session时,假如浏览器禁止 cookie 或不支持 cookie , 一般会把 sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现。
7. 单点登录实现
多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
这种需求需要解决跨域认证 以及 前端页面 JavaScript 跨域 问题
7.1 跨域认证解决
7.1.1 session持久化
如果采用传统的session用户验证机制,则需要session实现多系统共享。那么标准的实现方案就是 将 session 数据持久化,写入数据库或别的持久层。各种服务受到请求后,都向持久层请求数据。
具体实现流程参考:
1:用户在A王网站登录成功后,将 session 存储到 Redis 持久化存储,注意设置有效期。
2:此时访问 网站B 进行用户身份认证。注意 此时 访问 B的链接URL 要携带参数 sessionid!B 在处理请求- 身份验证时,先解析是否携带了sessionid参数,携带了则向 redis 中查询相关数据,并将数据保存到当前会话中。此时就成功 登录 B 了。
7.1.2 JWT方案
用户在A网站登录成功后,服务器在客户生成加密的Token(如包含下面的信息),然后此时访问B系统同样携带此Token。服务端统一通过算法进行解密验证即可。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2019年10月1日0点0分"
}
7.2 JS跨域解决
单点登录难免会遇到窗口之间 JS 跨域问题,此时的解决方案是 postMessage
postMessage 是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递
用法:postMessage(data,origin) 方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*“,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/"。
1.)a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById("iframe");
iframe.onload = function() {
var data = {
name: "aym"
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), "http://www.domain2.com");
};
// 接受domain2返回数据
window.addEventListener("message", function(e) {
alert("data from domain2 ---> " + e.data);
}, false);
</script>
2.)b.html:(http://www.domain2.com/b.html)
<script>
// 接收domain1的数据
window.addEventListener("message", function(e) {
alert("data from domain1 ---> " + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), "http://www.domain1.com");
}
}, false);
</script>