本文是对 Token 技术的总结。
目录
- 缘起
- 原理
- 优势
- 认证流程
- 注意事项
缘起
Token 的出现源于整个 Web 的发展历史:
- Web 最初出现只是为了浏览文档,从 HTTP 协议名称(HyperText Transfer Protocol,超文本传输协议)中便可看出,HTTP 1.0 版本起初即被设计为无状态。
- 随着 Web 交互式应用的兴起(如:在线购物),HTTP 协议无状态性的局限很快被发现,为了管理会话并保留期间的状态信息,很快想出了一个办法,即服务端为每个客户端生成一个会话标识(Session ID),此会话标识(Session ID)是个随机字符串,每个客户端收到的不一样,后续每个客户端再发起请求时必须连同此会话标识(Session ID)一起发送,这样服务端便可根据不同的会话标识(Session ID)区分请求来自于哪个客户端。
- 虽然通过会话标识(Session ID)已经能够区分不同的客户端请求,但是随着应用规模的增加,服务端面临了几个挑战:
- 因为服务端需要保存会话标识(Session ID),因此一旦客户端数量非常大时,服务端存储开销巨大;
- 当需要通过集群方案扩展服务端能力时,会话标识(Session ID)的共享又成了一个问题,因为客户端的请求可能会被转发到不同的服务端节点,譬如第一次请求转发到服务端节点 A,下一次请求被转发到服务器节点 B,那么 A 和 B 之间必须实现会话标识(Session ID)的共享,否则客户端在 A 已经认证通过后,转发到 B 后又要重新认证。尽管可以通过不同的负载均衡算法保证一个客户端的请求始终转发到一个固定的服务节点,但一旦 IP 发生改变则很可能又要进行重新认证。考虑到会话标识(Session ID)在多节点间复制的同步效率问题,通常会使用 Redis 等分布式系统集中缓存方案统一存放会话标识(Session ID),但这仍然依赖于 Redis 的可靠性,且存储空间消耗的问题也未解决。
为了避免服务端存储会话标识(Session ID),Token 应运而生。
原理
如果服务端不存储会话标识(Session ID),那么如何保证客户端请求中带的会话标识(Session ID)是有效的呢?如何避免伪造?Token 的关键在于其不可伪造。
Token 生成过程:
- 服务端认证通过客户端身份后,利用部分有效数据组成一段明文,如:
{"username":"admin"}
; - 服务端使用一种加密算法加上一个只有自己才知道的密钥对以上数据进行签名,生成签名字符串:
XXXXXX
; - 将明文和签名字符串组合成一个文本:
{"username":"admin"}XXXXXX
; - 对组合后的文本执行
BASE64
编码即得到 Token; - 将此 Token 直接返回给客户端,不在服务端保存;
- 客户端再次发送回请求后,取出其中的 Token,首先执行
BASE64
解码,然后分离出明文段和签名段,使用第 2 步中相同的加密算法和密码生成签名字符串,与解码后得到的签名段进行比较即可判断 Token 是否有效。
Token 实际上是一种 时间换空间 的方案,即利用 CPU 加密的计算时间换取 会话标识(Session ID)的存储空间。没有了 会话标识(Session ID)的负担,服务端的水平扩展便无需考虑集中存储的瓶颈,只需增减服务器数量即可。
优势
- 安全性
请求中发送 Token 而不再是发送 Cookie 能够防止 CSRF(跨站请求伪造),即使客户端使用 Cookie 存储 Token,此时的 Cookie 也只是用于存储而非认证。
Token 具备时效性,也可以撤回,通过 Revocation 可以使一个特定 Token 或一组有相同认证的 Token 失效。 - 无状态
Token 是无状态的,无需服务端存储。 - 可扩展
使用 Token 可以提供一定范围的权限给第三方服务,可以开放独立的 API 和特殊权限 Token 给第三方服务访问。 - 多平台跨域
通过将服务器属性设置为Access-Control-Allow-Origin: *
可实现数据和资源能够被任何域请求。 - 基于标准
参考:JSON Web Tokens
认证流程
- 客户端发送含有用户名和密码的身份认证请求;
- 服务端接收到请求后验证用户名和密码;
- 验证成功后服务端会生成一个 Token 并返回给客户端;
- 客户端将请求存储在 Cookie、或 LocalStorage 中,客户端在后续所有请求中都附带上该 Token;
- 服务端接收到请求后通过过滤器验证 Token 有效性。
注意事项
- Token 中的明文数据不能存放敏感信息。
- Token 应该放在 HTTP 的头部,这样才保证了 HTTP 请求的无状态。
- 注意服务器属性设置:
Access-Control-Allow-Origin: *
,让服务器能够接受来自所有域的请求。
网友评论