解析jwt实现逻辑

由于项目需求,需要将多个项目的部分功能类似的接口迁移到一个统一的项目中方便管理,每个项目都使用了tymon/jwt-auth扩展包进行token认证,所以需要自己重写token认证方式去兼容之前项目的认证而不修改影响到之前项目运行,所以深入了解了jwt的生成和解析逻辑,以便能够更好的重写相关逻辑。

什么是JWT

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

详细信息可查看什么是 JWT – JSON WEB TOKEN,这里就不做过多介绍。

在项目中我使用了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();
    }

总结:

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

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

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

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

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!