PHP中四種主流Token實(shí)現(xiàn)方案
Token 機(jī)制廣泛應(yīng)用于身份驗(yàn)證、防止 CSRF 攻擊、接口鑒權(quán)等場景。在 PHP 中,主流實(shí)現(xiàn)方案均圍繞 生成唯一標(biāo)識(shí) + 存儲(chǔ)驗(yàn)證 + 有效期控制三大核心思路展開。本文梳理了 4 種主流實(shí)現(xiàn)方式,便于快速選型和實(shí)踐。
方案 1:基于 Session 的 Token 實(shí)現(xiàn)
核心原理
利用 PHP 內(nèi)置 Session 機(jī)制,將 Token 存儲(chǔ)在服務(wù)端 Session 中,客戶端僅保存 SessionID(通常通過 Cookie)。每次請(qǐng)求時(shí),服務(wù)端驗(yàn)證 Token 是否匹配。
實(shí)現(xiàn)代碼
<?php
// 初始化Session
session_start();
/**
* 生成并存儲(chǔ)Token
*/
function generateSessionToken() {
$token = bin2hex(random_bytes(16));
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_expire'] = time() + 3600; // 1小時(shí)有效期
return $token;
}
/**
* 驗(yàn)證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驗(yàn)證通過,執(zhí)行業(yè)務(wù)邏輯';
} else {
echo 'Token無效或已過期';
}
}
?>優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
| 實(shí)現(xiàn)簡單,依賴 PHP 原生 Session | 依賴 Cookie,客戶端禁用 Cookie 則失效 |
| Token 存儲(chǔ)在服務(wù)端,安全性高 | 分布式部署需配置 Session 共享,如 Redis |
| 支持服務(wù)端主動(dòng)銷毀 Token | 占用服務(wù)器內(nèi)存(Session 默認(rèn)存儲(chǔ)在文件/內(nèi)存) |
方案 2:基于 JWT(JSON Web Token)的 Token 實(shí)現(xiàn)
核心原理
JWT 是一種 無狀態(tài) Token 方案,由 Header、Payload、Signature 三部分組成。Token 存儲(chǔ)于客戶端(如 LocalStorage/Cookie),服務(wù)端僅通過簽名校驗(yàn)其合法性和完整性。
前置條件
需安裝 JWT 擴(kuò)展,推薦使用 firebase/php-jwt:
實(shí)現(xiàn)代碼
<?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);
}
/**
* 驗(yàn)證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驗(yàn)證失敗:' . $e->getMessage();
return false;
}
}
// 示例
$userPayload = ['uid' => 1001, 'username' => 'test_user'];
$jwtToken = generateJwtToken($userPayload);
echo '生成的JWT Token:' . $jwtToken . '
';
$clientToken = $_GET['token'] ?? '';
$decodedData = verifyJwtToken($clientToken);
if ($decodedData) {
echo 'Token驗(yàn)證通過,用戶ID:' . $decodedData['uid'];
} else {
echo 'Token無效';
}
?>優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
| 無狀態(tài),分布式友好 | Token 無法主動(dòng)銷毀,僅能等過期(可用黑名單彌補(bǔ)) |
| 支持跨域、跨平臺(tái) | 負(fù)載信息 Base64 編碼,不可存儲(chǔ)敏感數(shù)據(jù) |
| 傳輸體積小,可自定義數(shù)據(jù) | 密鑰泄露風(fēng)險(xiǎn)高,需嚴(yán)格保管 |
| 不依賴 Cookie,兼容性好 | 過期時(shí)間固定,需重新簽發(fā)才能調(diào)整 |
方案 3:基于 Redis 的 Token 實(shí)現(xiàn)
核心原理
Token 作為 Key,用戶信息/過期時(shí)間作為 Value 存儲(chǔ)在 Redis。利用 Redis 過期鍵特性自動(dòng)管理 Token 有效期,驗(yàn)證時(shí)查詢 Redis 是否存在該 Token 且未過期。
前置條件
需安裝 PHP Redis 擴(kuò)展,并配置 Redis 連接。
實(shí)現(xiàn)代碼
<?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); // 滑動(dòng)有效期
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驗(yàn)證通過,用戶ID:' . $uid;
} else {
echo 'Token無效或已過期';
}
// destroyRedisToken($clientToken);
?>優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
| 支持分布式、集群部署 | 依賴 Redis,增加系統(tǒng)復(fù)雜度 |
| 可主動(dòng)銷毀 Token,安全性高 | Redis 性能或容災(zāi)需關(guān)注 |
| 支持滑動(dòng)有效期 | 需處理 Redis 連接異常,代碼需容錯(cuò) |
| 不依賴 Cookie,兼容性好 | 性能略低于 Session/JWT(需網(wǎng)絡(luò) IO) |
方案 4:基于數(shù)據(jù)庫(MySQL)的 Token 實(shí)現(xiàn)
核心原理
將 Token、用戶ID、創(chuàng)建/過期時(shí)間、狀態(tài)等信息存儲(chǔ)在 MySQL。驗(yàn)證時(shí)查詢數(shù)據(jù)庫,檢查 Token 是否存在、未過期且狀態(tài)有效。
數(shù)據(jù)庫表結(jié)構(gòu)
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 '創(chuàng)建時(shí)間(時(shí)間戳)', `expire_time` int(11) NOT NULL COMMENT '過期時(shí)間(時(shí)間戳)', `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='用戶令牌表';
實(shí)現(xiàn)代碼
<?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 '數(shù)據(jù)庫連接失?。? . $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 '數(shù)據(jù)庫Token:' . $token . '<br>';
$clientToken = $_SERVER['HTTP_TOKEN'] ?? '';
$uid = verifyDbToken($clientToken);
if ($uid) {
echo 'Token驗(yàn)證通過,用戶ID:' . $uid;
} else {
echo 'Token無效或已過期';
}
// destroyDbToken($clientToken);
?>優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
| 支持復(fù)雜 Token 管理(如批量禁用、查詢記錄) | 數(shù)據(jù)庫 IO 性能低于 Redis/Session,高并發(fā)下易成瓶頸 |
| 數(shù)據(jù)持久化,方便追溯 | 需手動(dòng)清理過期 Token,表數(shù)據(jù)易膨脹 |
| 可擴(kuò)展字段,便于審計(jì) | 分布式部署需考慮主從同步 |
| 支持主動(dòng)銷毀 Token | 代碼復(fù)雜度高,需處理事務(wù)、異常等 |
各方案對(duì)比匯總表
| 對(duì)比維度 | Session Token | JWT Token | Redis Token | 數(shù)據(jù)庫 Token |
| 存儲(chǔ)位置 | 服務(wù)端(文件/內(nèi)存) | 客戶端 | 服務(wù)端(Redis) | 服務(wù)端(MySQL) |
| 狀態(tài)特性 | 有狀態(tài) | 無狀態(tài) | 有狀態(tài) | 有狀態(tài) |
| 分布式支持 | 需 Session 共享 | 天然支持 | Redis 集群 | 主從同步/分庫分表 |
| 主動(dòng)銷毀 | 支持 | 不支持(需黑名單) | 支持 | 支持 |
| 性能 | 高 | 極高 | 中 | 低 |
| 復(fù)雜度 | 低 | 中 | 中 | 高 |
| 適用場景 | 小型項(xiàng)目、CSRF 防護(hù) | 前后端分離、無狀態(tài)服務(wù) | 中大型項(xiàng)目、分布式 | 審計(jì)、復(fù)雜權(quán)限、低并發(fā) |
| 安全性 | 高 | 中 | 高 | 高 |
總結(jié)與選型建議
選型核心原則
- 小型單節(jié)點(diǎn)項(xiàng)目:優(yōu)先選用 Session Token(實(shí)現(xiàn)簡單)。
- 前后端分離/跨域場景:優(yōu)先選用 JWT Token(無狀態(tài),易于擴(kuò)展)。
- 中大型分布式項(xiàng)目:優(yōu)先選用 Redis Token(性能與靈活性兼具)。
- 需審計(jì)/復(fù)雜管理場景:可選 數(shù)據(jù)庫 Token(持久化與擴(kuò)展性強(qiáng))。
安全性建議
- 所有 Token 建議通過 HTTPS 傳輸,防止明文泄露。
- JWT 密鑰需定期更換,且嚴(yán)密保管。
- Session/Redis/數(shù)據(jù)庫 Token 建議設(shè)置合理有效期,驗(yàn)證后一次性銷毀(CSRF 場景)或滑動(dòng)刷新(登錄態(tài)場景)。
混合使用場景
- 可結(jié)合 JWT + Redis,實(shí)現(xiàn)“無狀態(tài) + 可銷毀”機(jī)制:JWT 存儲(chǔ)基礎(chǔ)信息,Redis 存儲(chǔ) JWT 黑名單,兼顧性能與靈活性。
以上就是PHP中四種主流Token實(shí)現(xiàn)方案的詳細(xì)內(nèi)容,更多關(guān)于PHP Token主流實(shí)現(xiàn)方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
php實(shí)現(xiàn)無限級(jí)分類(遞歸方法)
當(dāng)你學(xué)習(xí)php無限極分類的時(shí)候,大家都覺得一個(gè)字“難”我也覺得很難,所以,現(xiàn)在都還在看,因?yàn)楣ぷ饕玫?,所以,就必須得研究研究?/div> 2015-08-08
WordPress中調(diào)試縮略圖的相關(guān)PHP函數(shù)使用解析
這篇文章主要介紹了WordPress中調(diào)試縮略圖的相關(guān)PHP函數(shù)使用解析,包括使用set_post_thumbnail_size來調(diào)整縮略圖的大小,需要的朋友可以參考下2016-01-01
php實(shí)現(xiàn)插入數(shù)組但不影響原有順序的方法
這篇文章主要介紹了php實(shí)現(xiàn)插入數(shù)組但不影響原有順序的方法,實(shí)例分析了php數(shù)組操作的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
php生成短網(wǎng)址/短鏈接原理和用法實(shí)例分析
這篇文章主要介紹了php生成短網(wǎng)址/短鏈接原理和用法,結(jié)合實(shí)例形式分析了php生成短網(wǎng)址/短鏈接的基本原理、實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2020-05-05
php中cURL?error?60:SSL?certificate?problem:?unable?to?
PHP中cURL錯(cuò)誤60通常表示SSL證書問題,即無法獲取本地頒發(fā)機(jī)構(gòu)證書,這通常是由于cURL無法驗(yàn)證遠(yuǎn)程服務(wù)器的SSL證書導(dǎo)致的,本給大家介紹了如何解決php中cURL?error?60,需要的朋友可以參考下2023-12-12
學(xué)習(xí)php設(shè)計(jì)模式 php實(shí)現(xiàn)抽象工廠模式
這篇文章主要介紹了php設(shè)計(jì)模式中的抽象工廠模式,使用php實(shí)現(xiàn)抽象工廠模式,感興趣的小伙伴們可以參考一下2015-12-12最新評(píng)論

