前言
维持用户会话状态的机制有 3 种:
维持用户会话状态的方案:cookie-session、JWT 等。
一、Cookie
1、什么是 Cookie?
Cookie 是浏览器存储的方式之一。
Cookie 的特点:
- 有有效期。Cookie 在生成时就会被指定一个 Expire 值(有效期),过期的 Cookie 会被浏览器自动清除。
- 由服务端生成。cookie 在用户第一次访问服务器时由服务器生成,并返给浏览器。
- 只能存储字符串。每个字符串都是以
key: value
的形式存储的。key 的名字是唯一的,相同名字时,后者会覆盖掉前者。 - 数量受限。一个浏览器可以存储多个 web 站点提供的 Cookie。不过,浏览器能创建的 Cookie 数量最多为 300 个,每个站点最多存放 20 个 Cookie,每个 Cookie 不能超过 4KB。
- cookie 是不可跨域的。 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。
- 不够安全。通常跨站点脚本攻击往往利用网站漏洞在网站页面中植入脚本代码或网站页面引用第三方法脚本代码,均存在跨站点脚本攻击的可能,在受到跨站点脚本攻击时,脚本指令将会读取当前站点的所有Cookie 内容(已不存在 Cookie 作用域限制),然后通过某种方式将 Cookie 内容提交到指定的服务器(如:AJAX)。一旦 Cookie 落入攻击者手中,它将会重现其价值。
- 受浏览器的管制。浏览器可以禁用 Cookie,禁用 Cookie 后,也就无法享有 Cookie 带来的方便。
Cookie 的适用场景(同时满足以下三个条件即可):
- 想要实现客户端与服务器之间的状态保持;
- 对安全性要求不高;
- 不需要存储大量的数据。
2、Cookie 的作用
Web 程序是使用 HTTP 协议传输的,而 HTTP 协议是无状态的协议,对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。Cookie 就是用来解决这些问题的。
3、Cookie 的工作原理
若浏览器没有禁用 Cookie,则其第一次发送请求到服务器时,服务器会生成 Cookie 并发送给浏览器,浏览器把 Cookie 以 key: value
的形式保存到客户端磁盘的一个文件中,之后,浏览器再次访问同一网站的服务器时会携带这个 Cookie,这样服务器就能通过 Cookie 中携带的数据来区分不同的用户了——这就好比:给每个客户端颁发一个通行证,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。
4、使用 cookie
请参见:Cookie 的使用的 Cookie 小节。
二、Session
1、什么是 Session?
session 是服务端存储的一个对象。
session 的主要作用是:通过存储访问者的信息来保持用户会话状态。
Session 的特点:
- 每个 session 都会有一个唯一标识 sessionid,根据客户端传过来的 cookie 中的 jsessionid,找到对应的服务器端的 session。
- 每个 session 的有效期是
30 分钟
,若有效期内客户端没有访问过该 session,服务器就认为该客户端已离线并删除该 session。 - 当服务器重启时,存储在内存中的 session 会被销毁,用户信息也就消失了。不过,可以通过将 session 存储到数据库中来持久化 session。
- Session 占用服务器性能,Session 过多,增加服务器压力。
2、Session 的工作机制
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
3、Session 常用的 API
常用的 session API | 描述 |
---|---|
getId() | 获取 sessionid |
invalidate() | 让 session 立刻失效 |
getAttribute(String key) | 根据 key 获取该 session 中的 value |
setAttribute(String key,Object value) | 往 session 中存放 key-value |
removeAttribute(Stringkey) | 根据 key 删除 session 中的 key-value |
getServletContext() | 得到 ServletContext |
setMaxInactiveInterval(time) / getMaxInactiveInterval() | 设置/获取 session 的最大有效时间 |
getCreationTime() | 获取 session 的创建的时间 |
getLastAccessedTime() | 获取 session 最后一次访问的时间 |
getSession() | 从 HttpServletRequest 中获取 session |
4、Session 的运用
需求:实现“跨浏览器的会话跟踪”
分析:因为 cookie 在多个浏览器之间是共享的(但是不能跨域),所以可以将 sessionid 存在 cookie 中,再把 cookie 存入磁盘中,然后在其他浏览器中再次访问该服务器时,就会读取到 cookie 中的 sessionid,从而回到上次访问的页面了。
实现:
session.setMaxInactiveInterval(2*3600); // session 保存俩小时
Cookie cookie=new Cookie("JSESSIONID",session.getId()); // sessionid 放到 cookie 中
cookie.setMaxAge(2*3600); // 客户端的 cookie 也保存俩小时
cookie.setPath("/"); // cookie 作用范围设为整个项目
response.addCookie(cookie); // 给浏览器返回该 Cookie
5、Session 的常见问题
(1)、如果需要禁止浏览器的 cookie 那么怎么进行 session 校验呢?
当浏览器 Cookie 被禁用时可以重写 URL 来将 jsessionid(其存储的也就是 sessionid) 传给服务器。
重写 URL 的方式有以下 2 种:
- 作为 URL 路径的附加信息(params)。
- 作为查询字符串附加在 URL 后面(query)。
可以使用 response 对象的 encodeURL() 或 encodeRedirectURL() 方法来重写 URL:
- response.encodeURL(String url):用于对表单 action 和超链接的 url 地址进行重写。
- response.encodeRedirectURL(String url):用于对 sendRedirect 方法后的 url 地址进行重写。
(2)、关闭浏览器后 session 会消失吗?
这个问题需要从以下 2 个方面考虑:
- 从服务器端考虑:我们知道session是存在于服务器端内存中的,和浏览器没有关系,所以浏览器关闭后,服务器端的session不会消失。(除非服务器重启或session达到了过期时间)。
- 从浏览器端考虑:我们知道浏览器是根据cookie中的jesessionid值来唯一找到服务器端的session的,此时若cookie没有持久化,浏览器关闭后cookie也跟着消失。所以当用户再次打开浏览器后,由于没有了cookie中的jesessionid,自然也无法唯一找到服务器端的session,对用户来说,确实是浏览器关闭后再次打开就无法找到上次的会话了,误以为是关闭浏览器后服务器端的session也跟着消失,其实还在。
三、Token
推荐阅读:
什么是token
深入理解 token
基于token机制鉴权架构
1、什么是 Token?
Token 是在服务端产生的。
token 可以翻译成"令牌",本质上它是一个全局唯一的字符串,用来唯一识别一个客户端。它是在 cookie 和 session 的基础之上延伸出来的一种维持用户会话状态的机制。
token 的主要作用是:维持用户会话状态。
Token 的优点:
- 无状态:token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
- 防止CSRF 攻击:CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造,属于网络攻击领域范围。构成这个攻击的原因,就在于 Cookie + Session 的鉴权方式中,鉴权数据(cookie 中的 session_id)是由浏览器自动携带发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。而 token 是通过客户端本身逻辑作为动态参数加到请求中的,token 也不会轻易泄露出去,因此 token 在 CSRF 防御方面存在天然优势。
- 适合移动应用:移动端上不支持 cookie,而 token 只要客户端能够进行存储就能够使用,因此 token 在移动端上也具有优势。
- 单点登录友好:使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 token 进行认证的话, token 被保存在客户端,完全由应用管理,所以它可以避开同源策略,不会存在跨域问题。
Token 的缺点:
- 无状态问题:token 的无状态,导致了它最大的缺点——当后端在token 有效期内用户Logout或者更改它的权限的话,不会立即生效,如果没有增加额外的处理逻辑,则一般需要等到有效期过后才可以。
- 性能问题:JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。
2、Token 解决了什么问题?
token解决了session依赖于单个Web服务器的问题。单体应用时用户的会话信息保存在session中,session存在于服务器端的内存中,由于前前后后用户只针对一个web服务器,所以没啥问题。但是一到了web服务器集群的环境下(我们一般都是用Nginx做负载均衡,若是使用了轮询等这种请求分配策略),就会导致用户小a在A服务器登录了,session存在于A服务器中,但是第二次请求被分配到了B服务器,由于B服务器中没有用户小a的session会话,导致用户小a还要再登陆一次,以此类推。这样用户体验很不好。当然解决办法也有很多种,比如同一个用户分配到同一个服务处理、使用cookie保持用户会话信息等。
3、Token 的工作机制
如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。
4、Token 的运用
使用 token 的 3 种使用方式:
- 用设备号/设备 mac 地址作为 Token(推荐)。
- 用 session 值作为 Token。
- 借助 JWT 第三方插件生成 token(推荐)。
借助 JWT 第三方插件生成 token 的案例:
import JWT from 'jsonwebtoken';
import { tokenBaseInfo } from '../config/crontab.config'
/**
* 创建 token
* @param {params}={需要携带进token的数据}
*/
export const createToken = (params) => {
const { secret, expires } = tokenBaseInfo;
// 创建一个 token
const token = JWT.sign({
id: params.id,
timestamp: (new Date()).getTime()
}, secret, {
expiresIn: expires//到期时间(秒)
});
return token;
}
/**
* 解析 token
*/
export const resolveToken = (token, secret) => {
return new Promise((resolve, reject) => {
JWT.verify(token, secret, (error, data) => {
error ? reject(error) : resolve(data);
});
})
}
四、Cookie、Session 和 Token 的异同
1、Cookie 和 Session的异同
推荐阅读:Cookie和Session的区别(面试必备)
session 和 cookie 密切相关,二者都生成于服务器。session 存储在服务器,而服务器会将自己生成的 sessionId 发送给浏览器,浏览器以jsessionid: <sessionid>
的形式将其存储到 cookie 中。可见,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
cookie 和 session 在实际运用中都是一起使用的。只不过在前端看来是 cookie,在后端看来是 session。所以可以将 cookie-session 归纳为一种 维持用户会话状态的方案。
(1)、Cookie 和 Session 的共同之处
- cookie 和 session 都是用来跟踪浏览器用户身份的会话方式。
- session 和 cookie 都由服务器生成。
(2)、Cookie 和 Session的区别
- 安全性:Cookie 有安全隐患,通过拦截或本地文件找得到用户的 Cookie 后可以进行攻击;Session 比 Cookie 更具有安全性。
- 存取值的类型不同:Cookie 只能存储 String 类型的对象;session 能够存储任意的 JavaScript 对象。
- 存储的地方不同:Cookie 可以存储在浏览器或者本地;Session 可以存储于服务器或数据库中。
- 存储大小不同:Cookie 有明确的大小限制(浏览器能创建的 Cookie 数量最多为 300 个,每个站点最多存放 20 个 Cookie,每个 Cookie 不能超过 4KB);而 Session 的大小与服务器的内存大小有关。
- 有效期不同:Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能;Session 一般失效时间较短(30 分钟),客户端关闭(默认情况下)或者 Session 超时都会失效。
Cookie | Session | |
---|---|---|
保存地址 | 客户端 | 服务端 |
声明周期 | 可以自己设置,默认到浏览器关闭 | 保存一定时间 |
存储内容 | 文本字符串 | 对象 |
存储大小 | <4KB | 没有限制 |
安全性 | 弱 | 强 |
应用场景 | 保存网站登录信息; 保存上次查看的页面; 浏览计数 | 网上商城的购物车; 保存用户登陆信息; 防止用户非法登陆。 |
五、JWT
JWT 是将认证后的数据保存在客户端,session 是保存在服务器端。
使用 JWT 替代 session 认证后,服务器不用维护 session,分布式环境下不需要单点存储 session。因为 JWT 的自解性,只要验证 JWT 是否合法就可以了。大大提升了系统的可扩展性,特别适用于微服务。
单纯的 JWT 无法实现 “踢人封号” 的需求。因为 JWT 是将认证后的数据保存在客户端,具有自解性。
那如果我们非要实现强制用户登出要怎么办呢?
可以采用类似oauth2.0协议中的做法,认证后颁布2个token,access token和refresh token。
- access token访问令牌为一个JWT,设置一个较短的过期时间,比如1小时。访问令牌每次调用后端服务都需要携带,往返网络的频率非常高,暴露的可能性就越大,设置较短的过期时间也可以降低安全风险。
- refresh token刷新令牌,可以不为JWT,设置一个较长的过期时间,比如1个月。刷新令牌主要用来换取新的access token。因为其仅在访问令牌要失效或已经失效时才会被传递给服务端,较长的过期时间并不会有太大的安全风险。颁发token的时候,仅将刷新令牌保存在redis并设置过期时间。当使用刷新令牌换取新的访问令牌时,需要判断redis里是否存在该刷新令牌,如果不存在,则刷新失败,用户就需要重新登录。
客户端要长时间维护登录态,就需要当访问令牌失效后,自动使用刷新令牌获取新的访问令牌。或者在访问令牌失效之前,提前刷新令牌。
现在我们想要踢人,只需要将用户相关的刷新令牌从redis里删除。当前的访问令牌失效后,自然也没有办法再刷新令牌了。从而达到强制用户登出的目的。
这么设计有个缺陷就是强制用户登出不是及时的。需要有一个等待访问令牌过期的时间。如果希望及时性高点,可以将访问令牌的过期时间设置短一点,但刷新token的频率就会升高。这个需要根据自己的业务进行权衡。
每次调用服务api时仍然是原汁原味的jwt无状态认证,无需访问任何中心存储。仅在刷新访问令牌的时候需要访问中心存储。也算是一种折中的方案。
JWT 的注销
jwt 一旦生成很难注销
-
清除 Cookie 但是服务端还会保存。
-
失效时间设置的短一点,让他自己失效。
六、单点登录
【推荐文章】
js 加密与解密汇总
一文彻底弄懂cookie、session、token
【参考文章】
做web开发,怎么能不懂cookie、session和token呢?
彻底理解cookie,session,token
还分不清 Cookie、Session、Token、JWT?
Cookie和Session的区别(面试必备)