JWT 是 JSON WEB TOKEN 的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的 JSON 对象,由于使用了数字签名,所以是可信任和安全的。
JWT token 的格式:header.payload.signature
。
header 中用于存放签名的生成算法:
{"alg": "HS512"}
payload 中用于存放用户名、token 的生成时间和过期时间,jwt 默认不加密,注意不要将秘密信息放在这个部分:
{"sub": "admin", "created": 1489079981393, "exp": 1489684781}
signature 为以 header 和 payload 生成的签名,需要指定一个密钥 secret:
String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)
可以在 https://jwt.io/ 解析 token:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NzA1MDEwMTQwOTUsImV4cCI6MTU3MTEwNTgxNH0.Tq9LIqSu8cet6ToqpTxS-sdY6bsCm0mWUFwKSgy5d0EovH4DYLxxqfswJpmibBNG0Ds2W0hM5D8BjVHHFeTTrg
Authorization
的头,值为 JWT 的 token;Authorization
头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。Authorization: Bearer <token>
相比于 Session 认证的方式来说,使用 token 进行身份认证主要有以下几个优势:
token 自身包含了身份验证需要的所有信息,使得服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。但是,也正是由于 token 的无状态,也导致了它最大的缺点:当后端在 token 有效期内废弃一个 token 或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户 Logout 的话,token 也还有效。除非,我们在后端增加额外的处理逻辑。
CSRF(Cross Site Request Forgery),即跨站请求伪造,属于网络攻击领域范围。什么是跨站请求伪造呢?简单来说就是用你的身份去发送一些对你不友好的请求。举个栗子:
小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。
<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科学理财,年盈利率过万</>
导致这个问题很大的原因就是:Session 认证中 Cookie 中的 session_id 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。
那为什么 token 不会存在这种问题呢?
一般情况下我们使用 JWT 的话,在登录成功获得 token 之后,一般会选择存放在 local storage 中。然后前端在请求头部加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。
但是这样会存在 XSS 攻击中被盗的风险,为了避免 XSS 攻击,你可以选择将 token 存储在标记为 httpOnly 的 cookie 中。但是,这样又导致了你必须自己提供 CSRF 保护。
具体采用上面哪两种方式存储 token 呢,大部分情况下存放在 local storage 下都是最好的选择,某些情况下可能需要存放在标记为 httpOnly 的 cookie 中会更好。
使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以不适合移动端。
但是,使用 token 进行身份认证就不会存在这种问题,因为只要 token 可以被客户端存储就能够使用,而且 token 还可以跨语言使用。
使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 token 进行认证的话,token 被保存在客户端,不会存在这些问题。
注销登录等场景下 token 还有效,与之类似的具体相关场景有:
这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 token 认证的方式就不好解决了。因为 token 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。那么如何解决这个问题呢?总结了下面几种方案:
对于修改密码后 token 还有效问题的解决还是比较容易的,可以使用用户的密码的哈希值对 token 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。
注意区分 JWT 与 Token+Redis 是两种不同的方案:
JWT:生成并发给客户端之后,后台是不用存储,客户端访问时会验证其签名、过期时间等再取出里面的信息(如 username),再使用该信息直接查询用户信息完成登录验证。jwt 自带签名、过期等校验,后台不用存储,缺陷是一旦下发,服务后台无法拒绝携带该 jwt 的请求(如踢除用户),属于 no session 系统。
token+redis:自己生成 <key,value> 用户信息,访问时判断 redis 里是否有该 token,如果有,则加载该用户信息完成登录。服务需要存储下发的每个 token 及对应的 value,维持其过期时间,好处是随时可以删除某个 token,阻断该 token 继续使用,不属于 no session 系统。