认证
认证即对用户提供的凭证或访问令牌进行鉴定的过程。常用认证方式有两种:基于 Token 和 基于 Session,本项目采用基于 Token 的认证方式。
#
基本概念- 身份凭证(Credential):用于证明用户身份的信息,通常为用户名及登录密码,也可以是证书、指纹。
- 认证(Authentication):对用户提供的凭证或访问令牌进行鉴定的过程。
- 授权(Authorization):认证成功后系统对用户是否能对指定资源执行指定操作的权限控制。
- 访问令牌(Access Token):完成认证授权后,用户每次请求操作服务器资源都应携带服务器发放的访问令牌,以使服务器确定用户身份及权限。
#
Token vs Session两种方式的核心区别在于存储于客户端的 Session ID 不是自解释的,从而导致服务器端必须持续存储 Session 数据。即 Session 认证方案是重服务器端方案,Token 方式则是将存储压力分散到客户端。
比较项目 | Token 认证 | Session 认证 |
---|---|---|
资源消耗 | 服务器 CPU 资源消耗(将存储空间的消耗分散到客户端) | 服务器 CPU 及内存资源消耗(用于实现 Session 的存储、查询、反序列化及回收) |
自解释,有效性不依赖于访问的服务器进程 | 认证信息被保存在服务器端,当使用集群化部署时,如果使用 Session 粘滞(即根据 IP 地址的哈希值将用户请求转发到特定的服务器进程)则会影响负载均衡的效果,不便于系统的水平扩展,否则,必须使用 Session 共享机制(如 Memcached) | |
高并发能力 | Token 存储于客户端,服务器内存占用率不会随着用户并发数量增加而增加 | Session 在服务器端内存中是长期贮存的,用户并发数量较大则会消耗大量服务器内存;较短的 Session 生命期将会导致频繁地要求用户登录认证,影响用户使用体验 |
#
JSON Web Token本项目采用 JWT(JSON Web Token、RFC 7519) 作为基于 Token 认证的解决方案。JWT 有如下特点:
- 安全性:使用 HMAC 或 RSA 算法加密。
- 小型:基于 JSON 格式数据(相较于 XML 等格式的数据体积小,且易于解释)。
- 自解释:可以包含所有必要信息,描述自我的有效性,从而在完成签名后无需再次查询数据库。
#
Token 的组成Token 由 Header、Payload、Signature 三部分组成,用点号进行链接。如:
#
基于 JWT 的认证流程:- 用户提供凭证向服务器申请授权。
- 服务器验证用户凭证,并在有效时取得用户信息。
- 对用户信息中的必要信息(如用户 ID、姓名、角色等)进行签名,生成 JWT 令牌,并返回给客户端。
- 客户端将服务器返回的令牌保存到 Cookie 或本地存储中,并在后续请求中携带。
- 用户发起操作服务器资源的请求,服务器对令牌进行校验、解析,并根据解析得到的数据判断用户的权限。
#
注意事项#
勿将非必要信息保存于 JWT 中虽然 JWT 不会占用服务器内存,但是将大量数据存储于 JWT 可能会导致 HTTP 请求的数据体积变大,从而阻塞网络。
#
勿将 JWT 用作 SessionJWT 的设计目的主要包含两方面,一是用作身份认证,一是用作数据交换。使用 JWT 作为 Session(保存状态数据)将导致服务器频繁的对数据进行签名(增加 CPU 资源开销),以及向客户端返回超大令牌数据(增加网络带宽开销)。
#
实践中的需求以上介绍的 JWT 的基本内容,在实际的项目中,根据需求还要进行改进调整。
#
服务端废除令牌(踢出)由于 JWT 具有自解释性,令牌一旦发行如果在有效期内服务端是无法对其无效化的。但是实际项目中要求服务端要具备将非法正在登录的用户踢出的能力。比如监控发现某用户正在发布非法图片,服务端需要马上废除这个用户的令牌。
#
解决方案- 对令牌进行数字签名时,生成一个令牌 ID,将其 保存到令牌和数据库中;数据库中存储数据的目的仅是验证其有效性,无需存储 JWT 装载的数据。
- 用户令牌校验时在数据库中查询是否存在,不存即视为令牌已无效。
- 通过管理端删除数据库中的令牌 ID 达到废止令牌的目的。
- 为了减少数据库的压力,采用缓存数据库(Redis)缓存令牌 ID来提高短时间内连续访问服务器时服务器端检查令牌存在性的效率。缓存数据库中记录的生命期可以设置为较短的时长(如 10 秒钟)。
#
用户信息更新同步如果用户信息被更新,那么已配发给客户端的令牌也需要及时更新。
#
解决方案- 令牌携带创建时间,并且在持久化数据和缓存中记录用户信息的最后更新时间。
- 当用户信息被更新时,及时更新用户所有令牌中的用户信息的最后更新时间。
- 服务器在发现客户端使用的令牌的创建时间早于用户最后更新时间时,应重新取得用户信息,对令牌重新进行数字签名,并将新的令牌返回给客户端。
#
设计与实现#
数据结构- JWT 令牌装载数据(payload)的数据结构
- 持久化数据(MongoDB)令牌记录数据结构
- 缓存(Redis)令牌记录存储内容
#
认证流程(登录)#
认证流程(鉴权)#
代码示例用户登录认证,生成访问令牌:
客户端发送请求
访问令牌校验