php Token 主流实现方案详解

Token 机制广泛应用于身份验证、防止 CSRF 攻击、接口鉴权等场景。在 PHP 中,主流实现方案均围绕 生成唯一标识 + 存储验证 + 有效期控制三大核心思路展开。本文梳理了 4 种主流实现方式,便于快速选型和实践。


方案 1:基于 Session 的 Token 实现

核心原理

利用 PHP 内置 Session 机制,将 Token 存储在服务端 Session 中,客户端仅保存 SessionID(通常通过 Cookie)。每次请求时,服务端验证 Token 是否匹配。

实现代码

php 复制代码
<?php
// 初始化Session
session_start();

/**
 * 生成并存储Token
 */
function generateSessionToken() {
    $token = bin2hex(random_bytes(16));
    $_SESSION['csrf_token'] = $token;
    $_SESSION['csrf_token_expire'] = time() + 3600; // 1小时有效期
    return $token;
}

/**
 * 验证Session Token
 */
function verifySessionToken($token) {
    if (!isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_token_expire'])) {
        return false;
    }
    if (time() > $_SESSION['csrf_token_expire']) {
        unset($_SESSION['csrf_token'], $_SESSION['csrf_token_expire']);
        return false;
    }
    $isValid = hash_equals($_SESSION['csrf_token'], $token);
    if ($isValid) {
        unset($_SESSION['csrf_token'], $_SESSION['csrf_token_expire']);
    }
    return $isValid;
}

// 示例
$token = generateSessionToken();
echo '<input type="hidden" name="csrf_token" value="'.$token.'">';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $clientToken = $_POST['csrf_token'] ?? '';
    if (verifySessionToken($clientToken)) {
        echo 'Token验证通过,执行业务逻辑';
    } else {
        echo 'Token无效或已过期';
    }
}
?>

优缺点

|------------------------|-----------------------------|
| 优点 | 缺点 |
| 实现简单,依赖 PHP 原生 Session | 依赖 Cookie,客户端禁用 Cookie 则失效 |
| Token 存储在服务端,安全性高 | 分布式部署需配置 Session 共享,如 Redis |
| 支持服务端主动销毁 Token | 占用服务器内存(Session 默认存储在文件/内存) |


方案 2:基于 JWT(JSON Web Token)的 Token 实现

核心原理

JWT 是一种 无状态 Token 方案,由 Header、Payload、Signature 三部分组成。Token 存储于客户端(如 LocalStorage/Cookie),服务端仅通过签名校验其合法性和完整性。

前置条件

需安装 JWT 扩展,推荐使用 firebase/php-jwt:

php 复制代码
composer require firebase/php-jwt

实现代码

php 复制代码
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$secretKey = 'your_strong_secret_key';
$algorithm = 'HS256';

/**
 * 生成JWT Token
 */
function generateJwtToken($payload = []) {
    global $secretKey, $algorithm;
    $defaultPayload = [
        'iat' => time(),
        'exp' => time() + 3600,
    ];
    $payload = array_merge($defaultPayload, $payload);
    return JWT::encode($payload, $secretKey, $algorithm);
}

/**
 * 验证JWT Token
 */
function verifyJwtToken($token) {
    global $secretKey, $algorithm;
    try {
        $decoded = JWT::decode($token, new Key($secretKey, $algorithm));
        return (array)$decoded;
    } catch (Exception $e) {
        echo 'Token验证失败:' . $e->getMessage();
        return false;
    }
}

// 示例
$userPayload = ['uid' => 1001, 'username' => 'test_user'];
$jwtToken = generateJwtToken($userPayload);
echo '生成的JWT Token:' . $jwtToken . '<br>';

$clientToken = $_GET['token'] ?? '';
$decodedData = verifyJwtToken($clientToken);
if ($decodedData) {
    echo 'Token验证通过,用户ID:' . $decodedData['uid'];
} else {
    echo 'Token无效';
}
?>

优缺点

|-----------------|-----------------------------|
| 优点 | 缺点 |
| 无状态,分布式友好 | Token 无法主动销毁,仅能等过期(可用黑名单弥补) |
| 支持跨域、跨平台 | 负载信息 Base64 编码,不可存储敏感数据 |
| 传输体积小,可自定义数据 | 密钥泄露风险高,需严格保管 |
| 不依赖 Cookie,兼容性好 | 过期时间固定,需重新签发才能调整 |


方案 3:基于 Redis 的 Token 实现

核心原理

Token 作为 Key,用户信息/过期时间作为 Value 存储在 Redis。利用 Redis 过期键特性自动管理 Token 有效期,验证时查询 Redis 是否存在该 Token 且未过期。

前置条件

需安装 PHP Redis 扩展,并配置 Redis 连接。

实现代码

php 复制代码
<?php
function initRedis() {
    $redis = new Redis();
    try {
        $redis->connect('127.0.0.1', 6379);
        // $redis->auth('your_redis_password');
        return $redis;
    } catch (Exception $e) {
        echo 'Redis连接失败:' . $e->getMessage();
        return null;
    }
}

function generateRedisToken($uid, $expire = 3600) {
    $redis = initRedis();
    if (!$redis) return false;
    $token = md5($uid . time() . random_bytes(16));
    $tokenKey = 'token:' . $token;
    $redis->setex($tokenKey, $expire, $uid);
    return $token;
}

function verifyRedisToken($token) {
    $redis = initRedis();
    if (!$redis) return false;
    $tokenKey = 'token:' . $token;
    $uid = $redis->get($tokenKey);
    if ($uid) {
        $redis->expire($tokenKey, 3600); // 滑动有效期
        return (int)$uid;
    }
    return false;
}

function destroyRedisToken($token) {
    $redis = initRedis();
    if (!$redis) return false;
    $tokenKey = 'token:' . $token;
    return $redis->del($tokenKey) > 0;
}

// 示例
$token = generateRedisToken(1001);
echo 'Redis Token:' . $token . '<br>';

$clientToken = $_POST['token'] ?? '';
$uid = verifyRedisToken($clientToken);
if ($uid) {
    echo 'Token验证通过,用户ID:' . $uid;
} else {
    echo 'Token无效或已过期';
}

// destroyRedisToken($clientToken);
?>

优缺点

|------------------|---------------------------|
| 优点 | 缺点 |
| 支持分布式、集群部署 | 依赖 Redis,增加系统复杂度 |
| 可主动销毁 Token,安全性高 | Redis 性能或容灾需关注 |
| 支持滑动有效期 | 需处理 Redis 连接异常,代码需容错 |
| 不依赖 Cookie,兼容性好 | 性能略低于 Session/JWT(需网络 IO) |


方案 4:基于数据库(MySQL)的 Token 实现

核心原理

将 Token、用户ID、创建/过期时间、状态等信息存储在 MySQL。验证时查询数据库,检查 Token 是否存在、未过期且状态有效。

数据库表结构

sql 复制代码
CREATE TABLE `user_token` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL COMMENT '用户ID',
  `token` varchar(64) NOT NULL COMMENT '令牌值',
  `create_time` int(11) NOT NULL COMMENT '创建时间(时间戳)',
  `expire_time` int(11) NOT NULL COMMENT '过期时间(时间戳)',
  `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1-有效 0-无效',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_token` (`token`),
  KEY `idx_uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户令牌表';

实现代码

php 复制代码
<?php
$dbConfig = [
    'host' => '127.0.0.1',
    'user' => 'root',
    'password' => 'your_db_password',
    'dbname' => 'test',
    'charset' => 'utf8mb4'
];

function initDb() {
    global $dbConfig;
    try {
        $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset={$dbConfig['charset']}";
        $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['password']);
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return $pdo;
    } catch (PDOException $e) {
        echo '数据库连接失败:' . $e->getMessage();
        return null;
    }
}

function generateDbToken($uid, $expire = 3600) {
    $pdo = initDb();
    if (!$pdo) return false;
    $token = hash('sha256', $uid . time() . random_bytes(32));
    $createTime = time();
    $expireTime = $createTime + $expire;
    $sql = "INSERT INTO user_token (uid, token, create_time, expire_time, status) VALUES (:uid, :token, :create_time, :expire_time, 1)";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':uid', $uid, PDO::PARAM_INT);
    $stmt->bindParam(':token', $token, PDO::PARAM_STR);
    $stmt->bindParam(':create_time', $createTime, PDO::PARAM_INT);
    $stmt->bindParam(':expire_time', $expireTime, PDO::PARAM_INT);
    return $stmt->execute() ? $token : false;
}

function verifyDbToken($token) {
    $pdo = initDb();
    if (!$pdo) return false;
    $now = time();
    $sql = "SELECT uid FROM user_token WHERE token = :token AND status = 1 AND expire_time > :now LIMIT 1";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':token', $token, PDO::PARAM_STR);
    $stmt->bindParam(':now', $now, PDO::PARAM_INT);
    $stmt->execute();
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($result) {
        return (int)$result['uid'];
    }
    return false;
}

function destroyDbToken($token) {
    $pdo = initDb();
    if (!$pdo) return false;
    $sql = "UPDATE user_token SET status = 0 WHERE token = :token";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':token', $token, PDO::PARAM_STR);
    return $stmt->execute() && $stmt->rowCount() > 0;
}

// 示例
$token = generateDbToken(1001);
echo '数据库Token:' . $token . '<br>';

$clientToken = $_SERVER['HTTP_TOKEN'] ?? '';
$uid = verifyDbToken($clientToken);
if ($uid) {
    echo 'Token验证通过,用户ID:' . $uid;
} else {
    echo 'Token无效或已过期';
}

// destroyDbToken($clientToken);
?>

优缺点

|---------------------------|------------------------------------|
| 优点 | 缺点 |
| 支持复杂 Token 管理(如批量禁用、查询记录) | 数据库 IO 性能低于 Redis/Session,高并发下易成瓶颈 |
| 数据持久化,方便追溯 | 需手动清理过期 Token,表数据易膨胀 |
| 可扩展字段,便于审计 | 分布式部署需考虑主从同步 |
| 支持主动销毁 Token | 代码复杂度高,需处理事务、异常等 |


各方案对比汇总表

|-------|---------------|-------------|-------------|-------------|
| 对比维度 | Session Token | JWT Token | Redis Token | 数据库 Token |
| 存储位置 | 服务端(文件/内存) | 客户端 | 服务端(Redis) | 服务端(MySQL) |
| 状态特性 | 有状态 | 无状态 | 有状态 | 有状态 |
| 分布式支持 | 需 Session 共享 | 天然支持 | Redis 集群 | 主从同步/分库分表 |
| 主动销毁 | 支持 | 不支持(需黑名单) | 支持 | 支持 |
| 性能 | 高 | 极高 | 中 | 低 |
| 复杂度 | 低 | 中 | 中 | 高 |
| 适用场景 | 小型项目、CSRF 防护 | 前后端分离、无状态服务 | 中大型项目、分布式 | 审计、复杂权限、低并发 |
| 安全性 | 高 | 中 | 高 | 高 |


总结与选型建议

  1. 选型核心原则
  • 小型单节点项目:优先选用 Session Token(实现简单)。
  • 前后端分离/跨域场景:优先选用 JWT Token(无状态,易于扩展)。
  • 中大型分布式项目:优先选用 Redis Token(性能与灵活性兼具)。
  • 需审计/复杂管理场景:可选 数据库 Token(持久化与扩展性强)。
  1. 安全性建议
  • 所有 Token 建议通过 HTTPS 传输,防止明文泄露。
  • JWT 密钥需定期更换,且严密保管。
  • Session/Redis/数据库 Token 建议设置合理有效期,验证后一次性销毁(CSRF 场景)或滑动刷新(登录态场景)。
  1. 混合使用场景
  • 可结合 JWT + Redis,实现"无状态 + 可销毁"机制:JWT 存储基础信息,Redis 存储 JWT 黑名单,兼顾性能与灵活性。

相关推荐
m0_748229992 小时前
Laravel5.x核心特性全解析
开发语言·php
河北小博博2 小时前
分布式系统稳定性基石:熔断与限流的深度解析(附Python实战)
java·开发语言·python
岳轩子2 小时前
JVM Java 类加载机制与 ClassLoader 核心知识全总结 第二节
java·开发语言·jvm
智航GIS2 小时前
ArcGIS Python零基础脚本开发教程---1.1 Describe 函数
开发语言·python·arcgis
云游云记2 小时前
php 网络请求工具全解:cURL 与 Guzzle 总结
开发语言·网络·php
m0_748229992 小时前
帝国CMS后台搭建全攻略
java·c语言·开发语言·学习
weixin_462446232 小时前
PaddleX 3.2 人脸识别实战:自定义人脸库 + CartoonFace 官方案例 Top-K 识别完整指南
开发语言·r语言
Testopia3 小时前
走一遍 AI 学习之路 —— AI实例系列说明
开发语言·人工智能·python
Tony Bai3 小时前
【分布式系统】11 理论的试金石:用 Go 从零实现一个迷你 Raft 共识
开发语言·后端·golang