JWT 的使用

概述

JSON Web Token(JWT)是一种轻量级的身份认证方案,适用于前后端分离架构中的身份校验场景。本文基于 Yii2 框架,介绍 JWT 的引入、代码实现、校验流程及安全防护措施,以快速集成 JWT。

环境准备:引入 JWT 组件

通过 Composer 快速安装 Firebase 提供的 PHP-JWT 组件,该组件兼容主流 PHP 版本,且集成简单、性能稳定。

composer require firebase/php-jwt

代码实现

以下代码实现了用户登录生成 Token、身份校验两大核心功能,可根据实际业务需求调整用户信息字段、过期时间等配置。

完整代码示例

<?php
declare(strict_types = 1);

namespace app\controllers;

use app\common\Helper;
use Firebase\JWT\JWT;
use yii\base\Controller;

class TestController extends Controller
{
    /**
     * 登录接口:验证身份并生成 JWT Token
     * @throws \Exception
     */
    public function actionLogin()
    {
        // 1. 实际场景需替换为真实的验证码、用户名密码校验逻辑
        $isValid = true; // 校验结果(true 为通过,false 为失败)
        if (!$isValid) {
            return Helper::msg(0, '登录失败,验证码、用户名或密码错误');
        }

        // 2. 校验通过后,获取数据库中的用户信息(示例数据)
        $userInfo = [
            'user_id' => 10000,       // 用户唯一ID
            'user_name' => 'haveyb',  // 用户名
            'is_admin' => 1,          // 是否为管理员(1=是,0=否)
            'menus' => [1, 2, 6, 13], // 用户权限菜单ID集合
        ];

        // 3. 生成 JWT Token(用于后续接口身份校验)
        $token = $this->generateJwtToken($userInfo);

        // 4. 返回给前端的响应数据
        $responseData = [
            'user_id' => $userInfo['user_id'],
            'user_name' => $userInfo['user_name'],
            'menus' => $userInfo['menus'],
            'token' => $token // 核心 Token 字段
        ];
        return Helper::msg(1, '登录成功', $responseData);
    }

    /**
     * 身份校验接口:验证 Token 有效性并返回用户信息
     * 实际场景中可被其他需要权限校验的接口调用
     */
    public function actionCheckAuth()
    {
        // 1. 获取前端传递的 Token(推荐放在请求头 Header 或 Cookie 中)
        $requestToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvd3d3LmhhdmV5Yi5jb20iLCJjcmVhdGVfdGltZSI6MTU4NjMzMzAwMSwidXNlcl9pZCI6MTAwMDAsImlzX2FkbWluIjoxfQ.VrGWf6nfhy8g5f-MufZK7J_OnOkUKiVHp4p9E9JavQw';

        // 2. 配置 JWT 密钥和加密算法(与生成 Token 时保持一致)
        $secretKey = $this->getJwtSecret();
        $algorithm = $this->getJwtAlgorithm();

        try {
            // 3. 验证 Token 签名有效性(抛出异常则说明 Token 被篡改或无效)
            $decodedData = (array)JWT::decode($requestToken, $secretKey, [$algorithm]);
        } catch (\Exception $e) {
            return Helper::msg(0, '身份校验失败:无效的 Token');
        }

        // 4. 校验 Token 过期时间(推荐必加,防止永久有效 Token 泄露风险)
        $expireTime = 86400; // 过期时间:24小时(单位:秒)
        if (time() - $decodedData['create_time'] > $expireTime) {
            return Helper::msg(0, '身份校验失败:Token 已过期,请重新登录');
        }

        // 5. 校验通过,返回用户身份信息(可进一步关联数据库查询最新用户数据)
        return Helper::msg(1, '身份校验成功', $decodedData);
    }

    /**
     * 生成 JWT Token 核心方法
     * @param array $userInfo  用户信息数组
     * @return string          生成的 Token 字符串
     */
    private function generateJwtToken(array $userInfo): string
    {
        $secretKey = $this->getJwtSecret();
        $algorithm = $this->getJwtAlgorithm();
        $currentTime = time();

        // Token 载荷(Payload):存储核心业务数据(避免敏感信息)
        $payload = [
            'iss' => 'https://www.haveyb.com', // 签发者(通常为项目域名)
            'create_time' => $currentTime,     // Token 创建时间(时间戳)
            'user_id' => $userInfo['user_id'], // 用户ID(核心标识)
            'is_admin' => $userInfo['is_admin']// 管理员标识(权限控制)
        ];

        // 生成并返回 Token
        return JWT::encode($payload, $secretKey, $algorithm);
    }

    /**
     * 获取 JWT 加密密钥(核心安全配置)
     * @return string 密钥字符串(建议通过环境变量存储,避免硬编码)
     */
    private function getJwtSecret(): string
    {
        // 示例密钥:建议替换为随机生成的32位以上字符串(如通过 openssl_random_pseudo_bytes 生成)
        return 'BQXSD0PxCFPN/3xa06/94PWm++0ZKOHw83kP0=';
    }

    /**
     * 获取 JWT 加密算法
     * @return string 算法名称(HS256 为 HMAC-SHA256,适用于对称加密场景)
     */
    private function getJwtAlgorithm(): string
    {
        return 'HS256'; // 对称加密算法,高效且易于实现(非对称加密可选用 RS256)
    }
}

代码关键说明

  1. 载荷(Payload)设计:存储非敏感核心数据(如用户ID、权限标识),避免存储密码、手机号等敏感信息;

  2. 密钥安全getJwtSecret() 方法返回的密钥是 Token 安全的核心,切勿泄露或硬编码在前端;

  3. 加密算法:HS256 为对称加密算法,前后端使用同一密钥;若需分布式部署或多服务协作,可选用 RS256 非对称加密;

  4. 过期时间:默认 24 小时过期,可根据业务需求调整(如后台管理系统可缩短至 1 小时)。

使用流程与校验结果

1. 登录获取 Token

  • 前端调用 actionLogin() 接口,传递用户名、密码、验证码等参数;

  • 后端校验通过后,返回包含 Token 的响应数据;

  • 前端存储 Token(如 localStorage、Cookie),用于后续接口请求。

2. 接口身份校验

  • 前端请求需要权限的接口时,在请求头(如 Authorization: Bearer {Token})中携带 Token;

  • 后端接口调用 actionCheckAuth() 方法,校验 Token 有效性;

  • 校验通过则继续业务逻辑,失败则返回错误信息(如 Token 过期、无效),要求用户重新登录。

3. 校验结果说明

  • 成功:返回状态码 1 及用户身份信息,允许接口访问;

  • 失败:返回状态码 0 及错误提示(如 Token 无效、过期),阻断接口访问。

五、安全防护措施

JWT Token 一旦泄露,攻击者可伪造用户身份操作,因此必须做好以下安全防护:

1. 强制使用 HTTPS 协议

  • 基于 SSL/TLS 加密传输,防止 Token 在网络传输过程中被窃听或篡改;

  • 部署时需配置有效的 SSL 证书(如 Let's Encrypt 免费证书)。

2. 强化密钥安全性

  • 密钥长度不少于 32 位,可通过 openssl_random_pseudo_bytes(32) 生成随机字符串;

  • 密钥通过环境变量(如 .env 文件)或配置中心存储,避免硬编码在代码中。

3. 严格校验 Token 过期时间

  • 必须添加过期时间校验(如示例中的 24 小时),缩短 Token 有效窗口;

  • 对于高安全性场景(如支付、后台管理),可设置更短的过期时间(如 30 分钟),并结合刷新 Token 机制。

4. 可选:添加 IP 绑定校验

  • 生成 Token 时,在载荷中添加用户 IP 字段(user_ip => $_SERVER['REMOTE_ADDR']);

  • 校验 Token 时,对比当前请求 IP 与载荷中的 IP,不一致则拒绝访问;

  • 注意:动态 IP 场景(如手机流量)不适用,需谨慎使用。

5. 其他补充防护

  • 避免在 Token 中存储敏感信息(如密码、身份证号);

  • 前端避免将 Token 存储在 localStorage(易受 XSS 攻击),优先使用 HttpOnly + Secure Cookie;

  • 定期更换密钥(需处理旧 Token 兼容问题,如设置过渡期)。

六、扩展建议

  1. 刷新 Token 机制:为避免用户频繁登录,可生成访问 Token(短期有效)和刷新 Token(长期有效),访问 Token 过期后,通过刷新 Token 重新获取;

  2. 分布式部署支持:若系统为分布式部署,使用 RS256 非对称加密算法,避免密钥同步问题;

  3. 日志记录:记录 Token 生成、校验失败等关键操作,便于排查安全问题;

  4. 权限细化:结合 Token 中的权限标识(如 is_adminmenus),实现接口级别的权限控制。