使用
jwt
也有一段时间了,感觉还是蛮方便的。写一篇文章总结一下。
JWT 是什么
JWT(JSON Web Token)是一个字符串,我们在发起网络请求时,将其放在header或者url中,这样可以保证传递的数据被篡改时能被我们发现,保证安全性。基于token的身份验证可以替代传统的cookie+session身份验证方法。注意:只能防止被修改,不能防止被解码。也就是说不能在 jwt 中存放重要数据。
JWT组成
JWT 由3部分组成,用点号连接。
header.payload.signature
header:两个字段都是必须的,
alg
字段指定了生成signature的算法,默认值为HS256
,可以自己指定其他的加密算法。经过base64encode
就可以得到 header。
{
"typ":"JWT",
"alg":"HS256"
}
- playload :官方说法,三个部分组成(Reserved claims,Public claims,Private claims)。是表明用户身份的数据,可以自己自定义字段,很灵活。经过
json_encode
和base64_encode
就可得到payload。
$token = [
#非必须。issuer 请求实体,可以是发起请求的用户的信息,也可是jwt的签发者。
"iss" => "http://example.org",
#非必须。issued at。 token创建时间,unix时间戳格式
"iat" => $_SERVER['REQUEST_TIME'],
#非必须。expire 指定token的生命周期。unix时间戳格式
"exp" => $_SERVER['REQUEST_TIME'] + 7200,
#非必须。接收该JWT的一方。
"aud" => "http://example.com",
#非必须。该JWT所面向的用户
"sub" => "jrocket@example.com",
# 非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。
"nbf" => 1357000000,
# 非必须。JWT ID。针对当前token的唯一标识
"jti" => '222we',
# 自定义字段
"GivenName" => "Jonny",
# 自定义字段
"name" => "Rocket",
# 自定义字段
"Email" => "jrocket@example.com",
];
- signature:加上设置好的迷药秘钥,将 header和 payload使用 header 中指定的加密算法加密。
连起来就是 JWT 了
原理
最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。
所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
基本用法
1. 优势
Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
2.使用流程
- 初次登录:用户初次登录,输入用户名密码
- 密码验证:服务器从数据库取出用户名和密码进行验证
- 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT
- 返还JWT:服务器的HTTP RESPONSE中将JWT返还
- 带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorizatio字段都要有值,为JWT
3.刷新策略
JWT 有一个过期时间和一个刷新时间。
在过期时间内,可以正常使用。如果超过了过期时间,就需要重新登录。
- 比较好的做法应该是:
活跃的用户应该在无感知的情况下在JWT失效后获取到新的JWT,携带这个新的JWT进行访问,而长时间不活跃的用户应该在jwt失效后需要进行重新的登录认证。
使用旧的JWT可以获取到新的JWT,新的JWT应该在header中携带,传递给客户端。
4.blacklist
这里好像有一个漏洞,如果刷新JWT,旧的JWT还在有效期内,是不是两个JWT都可以验证通过呢?
其实不会, 刷新之后,旧的JWT会放入blacklist,其实就是放入缓存。如果使用黑名单中的JWT访问,验证失败。
5.并发问题
如果当前 token_1 过期,先发起 a 请求,之后马上发起 b 请求。a 请求到服务器,服务器判断过期,刷新 token_1,之后返回 token_2 给 a 请求响应。这时候迟一点的 b 请求用的还是 token_1 ,服务器已经将此 token_1 加入黑名单,所以 b 请求无效。
JWT已经想到这种情况,我们只需要设置一个黑名单宽限时间 blacklist_grace_period 即可。这个时间应该设置的比较短,在几秒钟都是可以的。