什么是jwt?JWT的全称是JSONWebToken,是一个非常轻量级的规范。该规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。jwt的使用场景jwt的图比较广泛,比如授权,鉴权等。具体来说,如果我们有一个用户A想要邀请某个用户加入他的群,那么用户A需要生成一个邀请链接,链接的内容大致如下:https://host/group/{group_id}/invite/{invite_user}此时点击这个链接可以让用户加入群组,但是用户可以随意更改这个链接的参数,比如更改群组后面的ID加入任意othergroup,并更改邀请背后的邀请人等操作。所以这种URL是不安全的,所以在这种情况下,我们可以使用jwt创建一个安全的邀请链接。首先,简单的改一下URL,https://host/group/invite/{token}可以看到我们去掉了groupId和inviteUser参数,增加了token参数。可以想象groupId和inviteUser应该是包含在token里面的,如何实现这个看似神奇的token呢?我们来看看jwt的原理。jwt的组成、原理和实现在说jwt的原理之前,首先要知道jwt是由什么组成的。jwt构成一个JWT实际上是一个字符串,它由三部分组成,header,payload和signature。头部(Header)JWT需要一个头部,用来描述JWT最基本的信息,比如它的类型,签名使用的算法等。这也可以表示为一个JSON对象,如:{"typ":"JWT","alg":"md5"}将上面的json字符串用base64编码后,可以得到如下内容,我们称之为JWT的标头(Header)。eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==有效载荷(Payload)我们先将上述邀请入群操作描述为一个JSON对象。添加一些其他信息,以帮助将来接收此JWT的服务器了解此JWT。{“sub”:“1”,“iss”:“http://host/group/invite”,“iat”:1451888119,“exp”:1454516119,“nbf”:1451888119,“jti”:“37c107e4609ddbcc9c096ea5ee76c667”,"group_id":1,"invite_user":"A"}这里的前6个字段都是JWT标准定义的。sub:JWT的用户iss:JWT的发行者iat(issuedat):发行令牌的时间tokenexp(expires):令牌到期的时间??nbf(notbefore):在此之前无法接收和处理令牌timejti:JWTID为网络令牌提供唯一标识符。将上面的json字符串用base64编码后,可以得到如下内容,我们称之为JWT载荷(Payload)。eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9签名(Signature)在签名之前我们需要先得到用于签名的字符串,将头部和载荷使用.进行拼接(头部在前),得到用于签名的字符串eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9然后使用签名Themethodsignsthestringusedforsignaturetogetthefollowingstring,thatis,signature(Signature)NDljMzljOTkyOGNmYWU1NGEyZDYzMTk5NTNlNGEwZDA=Finally,usethestringusedforsignatureandsignature.Splicing(signatureafter)togetacompletetoken.However,thetokenatthistimedoesnotcarrytheuniquesignoftheissuer,soitcanbeforged.Asforhowtosolvethisproblem,wewilltalkaboutthespecificimplementationofjwtbelow.TheprincipleofjwtHowdoesjwtensuresecurity?Aftertalkingaboutthecompositionofjwtabove,Ibelieveyoualreadyknowwhatjwtisprobably---itisastring!!!Sohowcanthisstringbeguaranteednottobetamperedwith?Herewewillintroducesecret.回到上面的例子,在邀请用户入群的场景中,虽然我们上面把参数改成了token的形式,但是大家可能会发现,这样的token被别人拿走后,还是可以伪造一个类似的标记自己。因为此时的签名(Signature)没有发行者的唯一身份信息,而且所有数据都是明文形式,所以这样的签名是不安全的,需要在签名中加上一个秘密。发行人需要准备一个可以确认其身份的字符串,我们称之为secret。以md5作为签名方式为例(不推荐使用md5作为签名方式),我们只需要简单地将上面准备的签名串和secret拼接起来,然后进行md5计算即可。此时得到的签名会受到secret值的影响,所以即使后面有人夺取了token,他仍然不能随意篡改token的内容,因为他不知道secret和拼接方法,所以此时的token是安全的,无法被恶意篡改。$signatureString='笔';//原始数据$secret='apple';//发行者秘密$originSignature=md5($signatureString.'-'.$secret);print_r($signature);//苹果笔$signatureString='pen';//原始数据$secret='pineapple';//不同的秘密$fakeSignature=md5($signatureString.'-'.$secret);print_r($signature);//pineapple-pen//可见不同的secret会产生完全不同的签名,这样就可以保证我们的数据不被随意篡改~jwt传输的数据会不会泄露?是的,jwt的header和payload字段可以用Decoded(base64属于encoding,可以decode)。所以不建议使用jwt传输敏感信息,比如密码,容易被抓包解码,从而被窃取。secret一个字符串不足以描述发行者信息?我们可以将发行者信息描述为json,然后对json字符串进行编码,这样也可以得到一个secret字符串。jwt实现先来个最粗略的jwt实现最简单粗暴的jwtforphp实现类JWT{protected$headers;受保护的$payload;/***@return数组*/publicfunctiongetHeaders():array{return$this->headers;}/***@returnarray*/publicfunctiongetPayload():array{return$this->payload;}公共函数__construct(array$headers,array$payload){$this->setHeaders($headers);$this->setPayload($payload);}publicfunctionsetHeaders(array$headers):void{$this->headers=$headers;}publicfunctionsetPayload(array$payload):void{$this->payload=$payload;}/***获取用于签名的字符串**@returnstring*/publicfunctionsignatureStr():string{$headersStr=$this::encodeStr(json_encode($this->headers));$payloadStr=$this::encodeStr(json_encode($this->payload));返回“{$headersStr}.{$payloadStr}";}/***编码**@paramstring$string**@returnstring*/protectedstaticfunctionencodeStr(string$string):string{returnrtrim(strtr(base64_encode($string),'+/','-_'),'=');}/***密码**@paramstring$string**@returnstring*/protectedstaticfunctiondecodeStr(string$string):string{returnbase64_decode(strtr($string,'-_','+/'));}/***签名,此时的秘密为qbhy**@paramstring$string**@returnstring*/protectedstaticfunctionsignature(string$string):string{returnmd5($string.'qbhy');}/***校验签名**@paramstring$signStr*@paramstring$sign**@returnbool*/protectedstaticfunctioncheckSignature(string$signStr,string$sign):bool{returnstatic::signature($signStr)===$sign;}/***生成令牌**@r返回字符串*/publicfunctiontoken():string{$signStr=$this->signatureStr();$token=$signStr。'.'.$this::signature($signStr);返回$令牌;}/***从令牌获取数据**@paramstring$token**@return\App\Modules\JWT\JWT*@throws\App\Modules\JWT\JWTException*/publicstaticfunctionfromToken(string$token):JWT{$arr=explode('.',$token);if(count($arr)!==3){thrownewJWTException('tokenerror');}if(!static::checkSignature("{$arr[0]}.{$arr[1]}",$arr[2])){thrownewJWTException('signatureerror');}$headers=json_decode(static::decodeStr($arr[0]),true);$payload=json_decode(static::decodeStr($arr[1]),true);返回新的静态($headers,$payload);}}simple-jwt这里给大家介绍一个基于php的jwt扩展包---96qbhy/simple-jwt,这个包实现了完整的jwt规范,开箱即用,你可以在你的应用基础上添加jwt相关功能96qbhy/简单jwt我把simple-jwt分成了四个部分,Encoder(编码器)、Encrypter(签名器)、JWT、JWTManager。可以自行扩展Encoder和Encrypter,实现自己的编码加密方式。有兴趣的同学可以去github上96qbhy/simple-jwt看一下源码。有问题欢迎和我讨论,欢迎Issue和PR。有错误请指出,谢谢。
