JWT 全称叫 JSON Web Token, 是一个很是轻巧的规范。这个规范容许咱们使用 JWT 在用户和服务器之间传递安全可靠的信息。php
jwt
用图普遍,例如受权
、鉴权
等。具体一点的话,假如咱们有一个 A 用户想要邀请某用户进入本身的群组,此时 A 用户须要生成一条邀请连接,连接内容大体以下: https://host/group/{group_id}/invite/{invite_user}
html
此时这个连接点击进去虽然能够实现让用户加入群组,可是用户能够随意更改这个连接的参数,例如改改 group 后面的ID,从而加入其余任意群组,改改 invite 后面的邀请人等等操做。因此这种 URL 并非安全的,那么这种状况下,咱们就可使用 jwt
来实建立一个安全的邀请连接了。git
首先 URL 要简单改一下, https://host/group/invite/{token}
能够看到咱们去掉了 groupId 和 inviteUser 参数,添加了一个 token
参数,可想而知, groupId 和 inviteUser 应该是被包含进 token
里面了,如何实现这个看似很神奇的 token 呢? 咱们来看看 jwt 的原理吧。github
在讲 jwt 原理以前得先知道 jwt 由哪些东西组成。web
一个 JWT 实际上就是一个字符串,它由三部分组成,头部、载荷与签名。算法
JWT 须要一个头部,用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。这也能够被表示成一个 JSON 对象,如:json
{ "typ": "JWT", "alg": "md5" }
将上面的 json 字符串使用 base64 进行编码后,能够获得一下内容,咱们称其为 JWT 的头部(Header)。安全
eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==
咱们先将上面的邀请入群的操做描述成一个 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的标准所定义的。app
将上面的 json 字符串使用 base64 进行编码后,能够获得一下内容,咱们称其为 JWT 的载荷(Payload)。
eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9
在签名以前咱们须要先获得用于签名的字符串, 将头部和载荷使用 .
进行拼接(头部在前), 获得用于签名的字符串
eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9
而后使用签名方法对用于签名的字符串进行签名, 获得以下字符串,即 签名(Signature)
NDljMzljOTkyOGNmYWU1NGEyZDYzMTk5NTNlNGEwZDA=
最后把用于签名的字符串和签名使用 .
进行拼接(签名在后), 便可获得 一个完整的 token
。可是,此时的 token
没有带上签发者特有的标志,是能够被伪造的,至于如何解决这个问题咱们下面 jwt 具体实现会讲。
上面说完 jwt 组成,相信你已经知道 jwt 大概是个啥子东西了 --- 就是一个字符串!!!
那么这个字符串如何保证不被篡改呢 ? 这里就要引入 secret
了。
回到上面的例子,邀请用户入群这个场景,虽然咱们上面把 参数改为了 token 这种形式,可是你可能会发现,这样的 token 别人捕获了以后,任然能够本身伪造一个相似的 token ,由于此时的签名(Signature)
并无签发者特有的身份信息,全部数据都是明文的,因此这样签名是不安全的,应该加上 secret
进行签名。
签发者须要准备一个能够确认本身身份的字符串,这个字符串咱们称之为 secret
。以 md5
做为签名方法为例(并不建议使用 md5 做为签名方法)
,咱们只须要将上面准备的 用于签名的字符串简单的与 secret
进行拼接,而后进行 md5 计算,这时候获得的签名是受 secret
值影响的,因此即使他人捕获了以后 token
,他仍然不能随意篡改 token 的内容,由于他不知道 secret
和拼接方法,故此时的 token
是安全的,不可被恶意篡改的。
$signatureString = 'pen'; // 原始数据 $secret = 'apple'; // 签发者 secret $originSignature = md5($signatureString .'-'. $secret); print_r($signature); // apple-pen $signatureString = 'pen'; // 原始数据 $secret = 'pineapple'; // 不同的 secret $fakeSignature = md5($signatureString .'-'. $secret); print_r($signature); // pineapple-pen // 能够看到不同的 secret 会生成彻底不同的签名,这样咱们的数据就能够保证不能被随意篡改了~
是的,jwt 的头部和载荷字段均可以被解码(base64 属于编码,是能够被解码的)
。因此并不建议用 jwt 传输敏感信息,例如密码,由于这很容易被捕获后解码,从而被窃取。
咱们能够将 签发者信息描述成一个 json ,而后对这个 json 字符串进行编码,这样一样能够获得一个 secret 字符串。
先来一个最粗暴的 jwt 实现
jwt for php
实现class JWT { protected $headers; protected $payload; /** * @return array */ public function getHeaders(): array { return $this->headers; } /** * @return array */ public function getPayload(): array { return $this->payload; } public function __construct(array $headers, array $payload) { $this->setHeaders($headers); $this->setPayload($payload); } public function setHeaders(array $headers): void { $this->headers = $headers; } public function setPayload(array $payload): void { $this->payload = $payload; } /** * 获取用于签名的字符串 * * @return string */ public function signatureStr(): string { $headersStr = $this::encodeStr(json_encode($this->headers)); $payloadStr = $this::encodeStr(json_encode($this->payload)); return "{$headersStr}.{$payloadStr}"; } /** * 编码 * * @param string $string * * @return string */ protected static function encodeStr(string $string): string { return rtrim(strtr(base64_encode($string), '+/', '-_'), '='); } /** * 解码 * * @param string $string * * @return string */ protected static function decodeStr(string $string): string { return base64_decode(strtr($string, '-_', '+/')); } /** * 签名,此时的 secret 为 qbhy * * @param string $string * * @return string */ protected static function signature(string $string): string { return md5($string . 'qbhy'); } /** * 校验签名 * * @param string $signStr * @param string $sign * * @return bool */ protected static function checkSignature(string $signStr, string $sign): bool { return static::signature($signStr) === $sign; } /** * 生成 token * * @return string */ public function token(): string { $signStr = $this->signatureStr(); $token = $signStr . '.' . $this::signature($signStr); return $token; } /** * 从 token 中获取数据 * * @param string $token * * @return \App\Modules\JWT\JWT * @throws \App\Modules\JWT\JWTException */ public static function fromToken(string $token): JWT { $arr = explode('.', $token); if (count($arr) !== 3) { throw new JWTException('token 错误'); } if (!static::checkSignature("{$arr[0]}.{$arr[1]}", $arr[2])) { throw new JWTException('签名错误'); } $headers = json_decode(static::decodeStr($arr[0]), true); $payload = json_decode(static::decodeStr($arr[1]), true); return new static($headers, $payload); } }
这里先安利一下我写的一个基于 php 的 jwt 扩展包 --- 96qbhy/simple-jwt
, 这个包实现了完整的 jwt 规范,开箱即用,你能够基于 96qbhy/simple-jwt
来给你的应用添加 jwt 相关功能。
我把 simple-jwt 拆分红,Encoder(编码器)
、Encrypter(签名器)
、JWT
、JWTManager
四部分,你能够自行扩展 Encoder
、Encrypter
,从而实现本身的编码和加密方法,感兴趣的同窗能够去 github
看看源码 96qbhy/simple-jwt 。有问题欢迎与我讨论,同时欢迎 Issue
和 PR
。若有错误欢迎指出,谢谢。