关于 jwt 你应该知道的事情

JWT(JSON Web Token)是一种用于在用户和服务器间传递安全信息的轻巧规范。本文详细介绍了JWT的组成、工作原理及其PHP实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是 jwt ?

JWT 全称叫 JSON Web Token, 是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

jwt 使用场景

jwt 用图广泛,例如授权鉴权等。具体一点的话,假如我们有一个 A 用户想要邀请某用户进入自己的群组,此时 A 用户需要生成一条邀请链接,链接内容大致如下: https://host/group/{group_id}/invite/{invite_user}

此时这个链接点击进去虽然可以实现让用户加入群组,但是用户可以随意更改这个链接的参数,例如改改 group 后面的ID,从而加入其他任意群组,改改 invite 后面的邀请人等等操作。所以这种 URL 并不是安全的,那么这种情况下,我们就可以使用 jwt 来实创建一个安全的邀请链接了。

首先 URL 要简单改一下, https://host/group/invite/{token}
可以看到我们去掉了 groupId 和 inviteUser 参数,添加了一个 token 参数,可想而知, groupId 和 inviteUser 应该是被包含进 token 里面了,如何实现这个看似很神奇的 token 呢? 我们来看看 jwt 的原理吧。

jwt 的组成、原理及实现

在讲 jwt 原理之前得先知道 jwt 由哪些东西组成。

jwt 组成

一个 JWT 实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

头部 (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(issued at): 在什么时候签发的 token
  • exp(expires): token 什么时候过期
  • nbf(not before):token 在此时间之前不能被接收处理
  • jti:JWT ID为web token 提供唯一标识

将上面的 json 字符串使用 base64 进行编码后,可以得到一下内容,我们称其为 JWT 的载荷(Payload)。

eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9

签名(Signature)

在签名之前我们需要先得到用于签名的字符串, 将头部和载荷使用 . 进行拼接(头部在前), 得到用于签名的字符串

eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9

然后使用签名方法对用于签名的字符串进行签名, 得到如下字符串,即 签名(Signature)

NDljMzljOTkyOGNmYWU1NGEyZDYzMTk5NTNlNGEwZDA=

最后把用于签名的字符串和签名使用 . 进行拼接(签名在后), 即可得到 一个完整的 token。但是,此时的
token 没有带上签发者特有的标志,是可以被伪造的,至于如何解决这个问题我们下面 jwt 具体实现会讲。

jwt 原理

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 传输的数据会泄露 ?

是的,jwt 的头部和载荷字段都可以被解码(base64 属于编码,是可以被解码的)。所以并不建议用 jwt 传输敏感信息,例如密码,因为这很容易被捕获后解码,从而被窃取。

secret 一个字符串不足以描述签发者信息 ?

我们可以将 签发者信息描述成一个 json ,然后对这个 json 字符串进行编码,这样同样可以得到一个 secret 字符串。

jwt 实现

先来一个最粗暴的 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);
    }

}

simple-jwt

这里先安利一下我写的一个基于 php 的 jwt 扩展包 --- 96qbhy/simple-jwt , 这个包实现了完整的 jwt 规范,开箱即用,你可以基于 96qbhy/simple-jwt 来给你的应用添加 jwt 相关功能。

我把 simple-jwt 拆分成,Encoder(编码器)Encrypter(签名器)JWTJWTManager 四部分,你可以自行扩展 EncoderEncrypter,从而实现自己的编码和加密方法,感兴趣的同学可以去 github 看看源码 96qbhy/simple-jwt 。有问题欢迎与我讨论,同时欢迎 IssuePR
如有错误欢迎指出,谢谢。

<think>我们正在讨论JWTJSONWebToken)。根据用户的问题,他们想了解JWT的定义和使用方法。根据引用[1]:JWTJSONWebToken的缩写,是一个开放标准(RFC7519),定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。根据引用[3]:官方解释:JSONWebToken(JWT)是一种开放标准(RFC7519),它定义了一种紧凑且独立的方式,用于在各方之间以JSON对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。根据引用[4]:JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。因此,我们可以这样组织回答:1.定义:JWT是什么。2.结构:JWT由三部分组成(头部、载荷、签名),并简要说明每一部分。3.使用方法:包括如何生成、如何使用(例如在HTTP请求中如何携带)。4.使用场景:如授权(常见场景)、信息交换等。另外,根据引用[2]和引用[3]:JWT的用途包括授权(如用户登录后携带令牌访问资源)和信息交换(安全传输信息)。同时,我们需要生成相关问题。注意:回答中引用的段落末尾添加引用标识,例如[^1]。</think>### JWTJSON Web Token)详解 #### 一、JWT 定义 JWTJSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输JSON格式的信息[^1]。它通过数字签名(如HMAC或RSA)确保数据的完整性和可信性,具有**紧凑**(可通过URL传输)、**自包含**(载荷携带所有必要信息)和**可验证**三大特性[^3]。 #### 二、核心结构 JWT 由三部分构成,以点号`.`分隔: ```text Header.Payload.Signature ``` 1. **头部(Header)** 声明令牌类型和签名算法,例如: ```json { "alg": "HS256", // 签名算法(如HS256/RSA) "typ": "JWT" // 固定为JWT } ``` 经Base64Url编码后形成第一部分[^4]。 2. **载荷(Payload)** 携带实际数据(称为Claims),包含三类声明: - **标准声明**:预定义字段如`exp`(过期时间)、`sub`(主题) - **公共声明**:自定义但需避免冲突的字段 - **私有声明**:业务自定义数据(如`userId`) ```json { "sub": "1234567890", "name": "Alice", "admin": true } ``` 经Base64Url编码后形成第二部分[^4]。 3. **签名(Signature)** 对前两部分拼接后加密生成,确保防篡改: ```python signature = HMAC-SHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key) ``` 最终令牌格式:`Header.Payload.Signature`[^4]。 #### 三、使用方法 1. **生成流程**: ```mermaid graph LR A[构建Header] --> B[构建Payload] B --> C[生成Signature] C --> D[拼接三段Base64] ``` 2. **客户端传递**(常见场景): - **HTTP请求头**(推荐): ```http Authorization: Bearer eyJhbGci...<JWT令牌> ``` - URL参数或Cookie 3. **服务端验证**: ```python import jwt try: decoded = jwt.decode(token, secret_key, algorithms=["HS256"]) except jwt.ExpiredSignatureError: return "令牌过期" ``` #### 四、主要应用场景 1. **用户认证与授权** 用户登录后,服务端返回JWT,客户端后续请求携带该令牌访问受保护资源[^2]。 2. **跨域单点登录(SSO)** 因JWT自包含特性,可在多个域共享登录状态[^3]。 3. **安全信息交换** 通过数字签名确保传输数据未被篡改,适用于API通信[^1]。 #### 五、优势与风险 ✅ **优势**: - 无状态:服务端无需存储会话 - 跨语言支持:标准化的JSON格式 - 扩展性强:Payload可灵活添加业务数据[^2] ⚠️ **风险**: - 令牌泄露导致未授权访问 - 无法主动废止令牌(需依赖短期有效期) - 敏感信息不应存储在未加密Payload中 > 示例令牌: > `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkpvaG4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c` > *(Header红色/Payload蓝色/Signature紫色)[^4]*
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值