JWT源码实现逻辑详解

2023-06-01 00:00:00 逻辑 源码 详解

描述:

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519). 该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。


详细信息可查看什么是 JWT – JSON WEB TOKEN(https://www.jianshu.com/p/576dbf44b2ae),这里就不做过多介绍。

在项目中我使用了 tymon/jwt-auth 扩展包,所以根据对这个包进行源码分析,了解其具体的实现逻辑

通过集成这个包,我们可以在 config.auth.php 中修改看守器的驱动,将 driver 修改为 jwt

'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

在用户登录,可通过 Auth::guard('api')->attempt(['email'=>$'email,'password'=>$password]); 校验用户登录并返回 token


其中校验密码的过程,我们一般将用户的密码通过 bcrypt () 函数进行加密,通过 bcrypt () 函数即使密码相同,生成的字符串也不相同。然后通过 password_verify () 函数验证密码是否和散列值匹配。


用户登录校验完成后,会返回如下的字符串,这个字符串就是 token, 它分为三个部分,第一部分我们称它为头部(header), 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature),通过. 将字符串连接在一起。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9lbGVtZW50LXNob3AudGVzdFwvYWRtaW5cL2FwaVwvbG9naW4iLCJpYXQiOjE2MTE4OTAwNDUsImV4cCI6MTYxMjEwNjA0NSwibmJmIjoxNjExODkwMDQ1LCJqdGkiOiJMTnQ3N01yaXlNUDVKRmFaIiwic3ViIjoyLCJwcnYiOiJhMjNiNTczZGM3M2E0MDdlOGRlNTNiNDg2ZjM2ODg2YWRmNzBjNDgzIn0.B4bHALzb5lg0z-G2iU3wwiYb4r18-wUa0TVH_V1X1IE


通过查看源码实现,其中 encode () 用于生成 token,decode () 用于解析 token

/**
     * Create a JSON Web Token.
     *
     * @param  array  $payload
     *
     * @throws \Tymon\JWTAuth\Exceptions\JWTException
     *
     * @return string
     */
    public function encode(array $payload)
    {
        // Remove the signature on the builder instance first.
        $this->builder->unsign();
        try {
            foreach ($payload as $key => $value) {
                $this->builder->set($key, $value);
            }
            $this->builder->sign($this->signer, $this->getSigningKey());
        } catch (Exception $e) {
            throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
        }
        return (string) $this->builder->getToken();
    }

继续查看生成 token 逻辑

/**
     * Returns the resultant token
     *
     * @return Token
     */
    public function getToken(Signer $signer = null, Key $key = null)
    {
        //为声明的加密算法,这里为HS256,可在./config/jwt.php中配置algo参数修改
        $signer = $signer ?: $this->signer;
        //token加密私钥,只存储在服务端,也是实现token最重要的一环,可在./config/jwt.php中配置secret参数修改
        //一般通过php artisan jwt:secret命令生成
        $key = $key ?: $this->key;
        if ($signer instanceof Signer) {
            //在token头部添加加密算法
            $signer->modifyHeader($this->headers);
        }
        //生成token的第一部分和第二部分
        $payload = [
            //将["typ" => "JWT","alg" => "HS256"]数组转为字符串并进行base64加密形成token的header
            $this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->headers)),
            //其中的$this->claims为token的载荷数组转为字符串并进行base64加密形成token的payload,内容包括
            //iss: jwt签发者
            //sub: jwt所面向的用户 
            //exp: jwt的过期时间,这个过期时间必须要大于签发时间
            //nbf: 定义在什么时间之前,该jwt都是不可用的.
            //iat: jwt的签发时间
            //jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
            $this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->claims))
        ];
        //生成签名,使用hash_hmac($this->getAlgorithm(), $payload, $key->getContent(), true)生成签名,其中$this->getAlgorithm()为加密算法HS256,$payload为token的第一,二部分,$key为加密私钥
        $signature = $this->createSignature($payload, $signer, $key);
        if ($signature !== null) {
            //将签名进行base64加密返回
            $payload[] = $this->encoder->base64UrlEncode($signature);
        }
        return new Token($this->headers, $this->claims, $signature, $payload);
    }

查看解析 token 的代码

/**
     * Decode a JSON Web Token.
     *
     * @param  string  $token
     *
     * @throws \Tymon\JWTAuth\Exceptions\JWTException
     *
     * @return array
     */
    public function decode($token)
    {
        try {
            //解析token,将token分割为三部分头部,负载,签名。对数据进行base64解码并json_decode转为数组,若解析失败抛出异常
            $jwt = $this->parser->parse($token);
        } catch (Exception $e) {
            throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e);
        }
        //使用hash_equals内置函数对token解析的签名与hash_hmac($this->getAlgorithm(), $payload, $key->getContent(), true)比较是否相同,若相同则表示认证通过,不同则直接抛出异常
        if (! $jwt->verify($this->signer, $this->getVerificationKey())) {
            throw new TokenInvalidException('Token Signature could not be verified.');
        }
        //认证通过返回payload的内容
        return (new Collection($jwt->getClaims()))->map(function ($claim) {
            return is_object($claim) ? $claim->getValue() : $claim;
        })->toArray();
    }


总结:

1.以上就是生成和解析 token 的大致逻辑,其中加密的关键还是在于服务器生成的私钥,若私钥流失,客户端就可以自己根据逻辑生成 token。

2.payload 要防止存放敏感信息,因为该部分是客户端可解密的部分。

3.想自己造轮子也知道逻辑。

4.面试的时候问到也不虚了。


转:https://learnku.com/articles/54009


相关文章