JSONWebToken(JWT)是一个非常轻量级的规范。该规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。让我们想象一个场景。当用户A关注用户B时,系统会向用户B发送一封邮件,其中包含“点击这里关注用户A”的链接。链接的地址可以这样https://your.app.com/make-friend/?from_user=B&target_user=A上面的URL主要是通过URL来说明这个。当然,这样做有一个缺点,就是要求用户B必须先登录。能不能把这个过程简化一下,让用户B不用登录就可以完成这个操作。JWT让我们可以做到这一点。JWT的组成JWT实际上是一个字符串,由三部分组成,header、payload和signature。{"iss":"JohnWuJWT","iat":1441593502,"exp":1441594722,"aud":"www.example.com,"sub":"kevin@example.com","from_user":"B","target_user":"A"}这里的前五个字段是JWT标准定义的iss:JWT的发行者sub:JWT的目标用户aud:当事人接收JWTexp(expires):当它过期时,这里是一个Unix时间戳iat(issuedat):当它被发出时这些定义可以在标准中找到。执行上面的JSON对象到[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9注意:base64是一种编码,它是可以被翻译回原来的样子来的。它并不是一anencryptionprocess.Header(Header)JWTalsoneedsaheader,whichisusedtodescribethemostbasicinformationabouttheJWT,suchasitstypeandthealgorithmusedforsignature.ThiscanalsoberepresentedasaJSONobject.{"typ":"JWT","alg":"HS256"}Here,weindicatethatthisisaJWT,andthesignaturealgorithmweuse(mentionedlater)istheHS256algorithm.Base64encodingisalsoperformedonit,andthesubsequentstringbecomestheHeader(header)oftheJWT.eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9签名(签名)将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0最后,我们将上面拼接完的字符串($input)用HS256algorithm($algo)forencryption.Whenencrypting,wealsoneedtoprovideakey($secret).Forexample,ifweusethestringmySecretasthekey,thenwecangetourencryptedcontentastheJWTsignaturethroughthefollowingmethod:hash_hmac($algo,$input,$secret);//output:rSWamyAYwuHCo7IFAgd1oRpùSP7nzL7BF5t7ItqpKViM签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM于是,我们就可以将邮件中的URL改成https://your.app.com/make-fri...Inthisway,aftertheserververifiesthatthereisnoproblemwiththeJWTsignature,itcancompletetheoperationofuserBaddinguserAasafriendwithoutrequiringtheusertologinagain.Seeingthis,youshouldhavetwoquestions.Whatisthepurposeofthesignature?Base64isakindofencodinganditisreversible,sowouldn'tmyinformationbeexposed?ThepurposeofthesignatureThelaststepofthesignatureprocessisactuallytosigntheheaderandpayloadcontent.Ingeneral,encryptionalgorithmsalwaysproducedifferentoutputsfordifferentinputs.给定两个不同的输入,产生相同输出的概率极小。因此,如果有人对header和payload的内容进行解码,修改,再进行编码,那么新的header和payload的签名就会与之前的签名不同。而且,如果不知道服务器加密使用的密钥,得到的签名肯定会不一样。服务端应用收到JWT后,会先用同样的算法对header和payload的内容进行重新签名。那么服务器应用程序如何知道我们使用的是哪种算法呢?不要忘记,我们已经在J??WT标头中使用alg字段指定了我们的加密算法。如果服务端应用以同样的方式再次对header和payload进行签名,发现自己计算出的签名与接收到的签名不一样,说明Token的内容已经被别人动过,我们应该拒绝这个Token并返回HTTP401Unauthorized响应。推荐使用PHP自带的函数hash_equals来帮助我们完成hash_equals(string$known_string,string$user_string):bool比较两个字符串,不管是否相等,这个函数耗时是常数.该函数可用于需要防止定时攻击的字符串比较场景,例如crypt()密码哈希值比较场景。这些信息会暴露JWTpayload,header中的信息可以通过base64解码得到,所以在JWT中,你不应该在payload中添加任何敏感数据。在上面的示例中,我们传递了用户的用户ID。这个值其实并不敏感,知道了一般是安全的。JWT中不能放密码等内容。如果你把用户的密码放在JWT中,那么恶意的第三方就可以通过Base64解码快速知道你的密码。JWT的适用场景我们可以看到,JWT适用于向Web应用传递一些非敏感信息。比如上面提到的添加好友的操作中,也经常使用JWT来设计用户认证授权系统,尤其是在现在的前后端分离开发架构中。用户登录成功后,会携带用户logo、登录时间、站点的JWT等信息给前端,前端在API请求的HEADER头中携带JWT。服务器验证JWT的合法性后,可以通过用户标识找到API请求所属的用户。之前用PHP写了一个很简单的JWT生成和校验程序,可以作为参考。代码放在要点https://gist.github.com/kevin…。
