3 JWT

未匹配的标注

1 概念

基于 token 的用户认证原理:让用户输入账号和密码,认证通过后获得一个token(令牌),在 token 有效期里用户可以带着 token 访问特定资源。

一开始 token 并没有一个统一标准,大家都各自使用自己的方案,后来出现了JWT( Json Web Token )这个标准。

2 JWT的结构

典型的JWT由三个部分组成,每个部分由一个点(.)分隔。

  • Header
  • Payload
  • Signature

2.1 Header

头部包含所使用的签名算法和令牌的类型(即JWT),这部分会被编码为Base64URL格式。

{
    "alg": "HS256",
    "typ": "JWT"
}

2.2 Payload

Playload 包含实际要传输的信息,附带一些其他信息如过期时间、发行时间等。JWT 指定了一些官方字段(claims)备用,如下所示。

除了官方字段,在这个部分还可以添加自定义字段。

iss: 签发人
exp: 过期时间
iat: 签发时间
nbf: 生效时间
jti: 编号
sub: 主题
aud: 受众

2.3 Signature

Signature 部分是对前两部分的防篡改签名。将 Header 和 Payload 用 Base64URL 编码后,再用点(.)连接起来。然后使用签名算法和密钥对这个字符串进行签名。

signature = hmac_sha256(base64encode(header) + '.' + base64encode(payload), 'SECRET_KEY')

3 PHP实现

基于 tp5 框架,配置多套加密算法,对 payload 数据随机使用算法加密,保证数据安全。

$data 数据:

[
    'header'  => ['random' => rand(0, 2), 'type' => 'JWT'],
    'payload' => ['data' => unique(), 'exp' => strtotime('+30 minutes', time())]
]

jwt.php 配置文件:

<?php
/**
 * 参数说明:
 *   secret          密钥
 *   jwt_plan        payload数据的加密方案
 *     - alg         算法
 *     - secrete     密钥
 *     - iv          向量。base64_encode()后的值,使用时需要 base64_decode()
 *                   使用 openssl_cipher_iv_length()、openssl_random_pseudo_bytes()生成
 */
return [
    'secret'       => '这里是填使用md5算法时的密钥',
    'jwt_plan'     => [
        0 => [
            'alg'    => 'aes-128-cbc',
            'secret' => '这里是填使用aes-128-cbc算法时的密钥',
            'iv'     => '这里是base64_encode后的iv向量',
        ],
        1 => [
            'alg'    => 'aes-128-ccm',
            'secret' => '这里是填使用aes-128-ccm算法时的密钥',
            'iv'     => '这里是base64_encode后的iv向量'
        ],
        2 => [
            'alg'    => 'aes-256-ofb',
            'secret' => '这里是填使用aes-256-ofb算法时的密钥',
            'iv'     => '这里是base64_encode后的iv向量'
        ]
    ],
];

生成 JWT:

private function jwtSignature($data)
{
    $config = config('jwt.');

    // 根据 random 值,获取对应的加密方案,对 payload 加密
    $jwtPlan    = $config['jwt_plan'][$data['header']['random']];
    $payloadRaw = openssl_encrypt(
        json_encode($data['payload']), 
        $jwtPlan['alg'], 
        $jwtPlan['secret'], 
        0, 
        base64_decode($jwtPlan['iv'])
    );

    // 字符+和/在URL中不能直接作为参数,把字符+和/分别变成-和_
    $header  = rtrim(strtr(base64_encode(json_encode($data['header'])), '+/', '-_'), '=');
    $payload = rtrim(strtr($payloadRaw, '+/', '-_'), '=');

    // 签名
    $signature = md5($header.$payload.$config['secret']);

    // 返回 JWT
    return "{$header}.{$payload}.{$signature}";
}

验证 JWT:

private function jwtSignatureValidation($jwtStr)
{
    try {
        $config = config('jwt.');

        list($headerRaw, $payloadRaw, $signature) = explode('.', $jwtStr);

        // 校验签名
        $toBeCheck = md5($headerRaw.$payloadRaw.$config['secret']);
        if ($toBeCheck != $signature) return false;

        // 解密 payload
        $header  = json_decode(base64_decode(strtr($headerRaw, '-_', '+/')), true);
        $jwtPlan = $config['jwt_plan'][$header['random']];
        $payload = json_decode(
            openssl_decrypt(
                strtr($payloadRaw, '-_', '+/'),
                $jwtPlan['alg'], $jwtPlan['secret'],
                0, 
                base64_decode($jwtPlan['iv'])
            ), true);

    } catch (\Exception $exception) {
        Log::record('jwtSignatureValidation_catch'.$exception->getMessage());
    }

    // 检查过期时间
    return $payload['exp'] > time() ? $payload['data'] : false;
}

4 JWT怎么用

浏览器接收到服务器发过来的 jwt 后,可以存储在 Cookie 或 localStorage 中。之后,浏览器每次与服务器通信时都会带上 JWT。可以将 JWT 放在 Cookie 中,会自动发送(不跨域),或将 JWT 放在 HTTP 请求头的授权字段中,也可放在 URL 或 POST 请求的数据体中。

如果文章有帮到你的话,别忘了点赞收藏噢:smile:

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~