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 黑名单,兼顾性能与灵活性。

相关推荐
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理6 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1236 天前
matlab画图工具
开发语言·matlab
dustcell.6 天前
haproxy七层代理
java·开发语言·前端