PHP基于JWT实现API身份验证流程

作者:技术拾光时间:2026-06-07 08:12:07浏览:246

JWT由Header、Payload和Signature三部分组成,用于API无状态身份验证,通过签名防篡改。采用PHP7.4+、Composer及firebase/php-jwt库,该库提供签名与验证功能。用户注册时用password_hash()加密密码,登录后生成带过期时间的JWT令牌,服务端验证令牌确保数据传输安全与防伪造,并支持刷新。

1. JWT简介

在了解 JWT(JSON Web Token)之前,先看看一个典型的 JWT 令牌长什么样。它由三个部分组成,用点号连接,就像这样:xxxxx.yyyyy.zzzzz。这三个部分各自承担着不同的角色,构成了 JWT 身份验证的基础。

PHP基于JWT实现API身份验证流程

Header – 这部分声明令牌类型和签名算法,比如 HMAC SHA256 或 RSA,是 JWT 令牌的头部信息。

Payload – 这部分存放实际的数据声明(claims),包括注册声明、公共声明和私有声明三种。这里携带了用户身份等关键信息,是 JWT 的核心内容。

Signature – 这部分对前两部分进行签名,保证令牌在传输过程中没有被篡改过,确保 JWT 的安全性。

理解这三个部分,就等于拿到了 JWT 的入场券。接下来,我们动手搭建一个完整的 PHP 环境下 JWT 验证流程。

2. 环境准备

工欲善其事,必先利其器。建议 PHP 版本在 7.4 以上,并且装好 Composer。为了简化 JWT 的生成和验证,我们选用 firebase/php-jwt 这个成熟库,它能高效处理 JWT 令牌的核心操作。

composer require firebase/php-jwt

3. 用户注册与登录

3.1 数据库设计

既然是身份验证,用户表少不了。这里用 MySQL,建一个最简单的 users 表,用于存储 JWT 身份验证所需的用户信息:

CREATE TABLE `users` (  
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `username` VARCHAR(50) NOT NULL UNIQUE,
  `password` VARCHAR(255) NOT NULL
);

注意:‌ 密码绝对不能明文存储。使用 PHP 内置的 password_hash() 函数进行哈希处理,这是 JWT 验证流程中最基本的安全底线。

3.2 注册接口

注册逻辑很直接:接收用户名和密码,哈希密码后写入数据库。不过要先检查用户名是否已被占用,避免重复注册。注册成功后即可用于后续的 JWT 登录生成令牌。

prepare("SELECT id FROM users WHERE username = ?");
    $stmt->execute([$username]);
    if ($stmt->fetch()) {
        return ['error' => 'User already exists'];
    }
    // 哈希密码
    $hash = password_hash($password, PASSWORD_DEFAULT);
    $stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
    $stmt->execute([$username, $hash]);
    return ['message' => 'User registered successfully'];
}

3.3 登录接口

登录时验证密码,验证通过后生成一个 JWT 令牌返回。密钥 secret_key 必须足够复杂,并且绝对不能硬编码在代码里——用环境变量来管理,这是保证 JWT 签名安全的关键。

function login($username, $password) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT id, password FROM users WHERE username = ?");
    $stmt->execute([$username]);
    $user = $stmt->fetch();
    if (!$user || !password_verify($password, $user['password'])) {
        return ['error' => 'Invalid credentials'];
    }

    // 生成JWT
    $secret_key = "your_secret_key"; // 应该是一个复杂的随机字符串,存储在环境变量中
    $payload = [
        'iss' => 'localhost',         // 签发者
        'aud' => 'localhost',         // 接收方
        'iat' => time(),              // 签发时间
        'exp' => time() + 3600,       // 过期时间(1小时)
        'data' => [
            'user_id' => $user['id'],
            'username' => $username
        ]
    ];
    $jwt = JWT::encode($payload, $secret_key, 'HS256');
    return ['token' => $jwt];
}

4. 验证JWT

当客户端请求需要登录的接口时,比如获取个人信息,它需要在 HTTP 头部带上 Authorization 字段,值形如 Bearer xxxxx.yyyyy.zzzzz。服务端拿到后解码并验证签名,这是 JWT 验证流程的核心步骤。

function getProfile() {
    // 获取Authorization头
    $headers = getallheaders();
    if (!isset($headers['Authorization'])) {
        http_response_code(401);
        return ['error' => 'Token not provided'];
    }
    $token = str_replace('Bearer ', '', $headers['Authorization']);
    $secret_key = "your_secret_key";
    try {
        $decoded = JWT::decode($token, new FirebaseJWTKey($secret_key, 'HS256'));
        // 验证成功后,用$decoded->data->user_id去数据库取用户信息
        $user_id = $decoded->data->user_id;
        // ... 数据库查询代码 ...
        return ['user' => $userInfo];
    } catch (Exception $e) {
        http_response_code(401);
        return ['error' => 'Invalid token'];
    }
}

注意 decode 使用的是新版的 Key 对象写法——Firebase JWT 库更新后的标准用法就是这样,确保 JWT 验证的正确性。

5. 安全性考虑

  • 使用 HTTPS‌ JWT 本质上还是个 Token 明文在网络里跑,没有 HTTPS 就意味着裸奔,务必加上 TLS/SSL 加密通道,防止 JWT 令牌泄露。
  • 密钥安全‌ 密钥(secret_key)必须足够复杂(比如 128 位随机字符串),并且用环境变量或者配置中心管理,绝不要硬编码提交到版本库,这是 JWT 签名安全的基本要求。
  • 令牌过期‌ 设置合理的有效期(比如 1 小时),并搭配刷新令牌机制,避免用户频繁重新登录,同时也能降低 JWT 令牌被盗用后的风险窗口。
  • 存储方式‌ 客户端通常把 JWT 放在 localStorage 或 sessionStorage 里,但这容易被 XSS 攻击窃取。更好的选择是用 HttpOnly 的 Cookie 存储,能有效防止 XSS,不过这时候要注意防范 CSRF 攻击,需要额外加 anti-CSRF token 等措施,确保 JWT 身份验证的整体安全。

6. 总结

至此,我们完整走了一遍在 PHP 中使用 JWT 做 API 身份验证的流程:从用户注册登录到 JWT 令牌生成,再到受保护接口的 JWT 验证。JWT 最大的好处是无状态、跨域友好,特别适合前后端分离和微服务架构。当然,JWT 安全不是一套就能结束的——输入验证、防 SQL 注入、定期轮换密钥等配套措施一个都不能少。希望这份流程能给你一个扎实的起点,剩下的细节可以在实际项目中慢慢打磨。