本文目录导读:

在 PHP 中实现令牌(Token)安全,主要涉及 CSRF(跨站请求伪造)防护 和 API 身份验证,下面我会分场景详细说明,并给出可直接运行的代码示例。
CSRF 令牌(表单安全)
基础实现(Session 存储)
<?php
session_start();
// 生成令牌
function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 验证令牌
function verifyCsrfToken($token) {
return isset($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
// 使用示例 - 生成表单
$token = generateCsrfToken();
?>
<form method="POST" action="process.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>">
<!-- 表单其他字段 -->
<button type="submit">提交</button>
</form>
<?php
// 处理提交时验证
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
die('CSRF 验证失败');
}
// 处理业务逻辑
}
?>
双令牌机制(更安全)
<?php
session_start();
// 生成双令牌
function generateDoubleCsrf() {
$sessionToken = bin2hex(random_bytes(32)); // 存 Session
$valueToken = bin2hex(random_bytes(32)); // 存表单
$_SESSION['csrf_session_token'] = $sessionToken;
$_SESSION['csrf_value_token'] = $valueToken;
return [
'session_token' => $sessionToken,
'value_token' => $valueToken
];
}
// 验证双令牌
function verifyDoubleCsrf($formSessionToken, $formValueToken) {
return hash_equals($_SESSION['csrf_session_token'], $formSessionToken)
&& hash_equals($_SESSION['csrf_value_token'], $formValueToken);
}
// 生成
$tokens = generateDoubleCsrf();
?>
<form method="POST">
<input type="hidden" name="session_token" value="<?= $tokens['session_token'] ?>">
<input type="hidden" name="value_token" value="<?= $tokens['value_token'] ?>">
</form>
API 令牌(身份验证)
Bearer Token(标准做法)
<?php
// 生成 API Token
function generateApiToken($userId) {
$secretKey = '你的服务器端密钥'; // 存环境变量
$payload = json_encode([
'user_id' => $userId,
'exp' => time() + 3600, // 1小时过期
'iat' => time()
]);
$base64Payload = base64_encode($payload);
$signature = hash_hmac('sha256', $base64Payload, $secretKey);
return $base64Payload . '.' . $signature;
}
// 验证 API Token
function verifyApiToken($token) {
$parts = explode('.', $token);
if (count($parts) !== 2) return false;
[$base64Payload, $signature] = $parts;
$expectedSignature = hash_hmac('sha256', $base64Payload, $secretKey);
// 验证签名
if (!hash_equals($expectedSignature, $signature)) {
return false;
}
// 验证过期时间
$payload = json_decode(base64_decode($base64Payload), true);
if ($payload['exp'] < time()) {
return false;
}
return $payload['user_id'];
}
// 使用中间件验证请求
function authenticateRequest() {
$headers = getallheaders();
$token = str_replace('Bearer ', '', $headers['Authorization'] ?? '');
$userId = verifyApiToken($token);
if (!$userId) {
http_response_code(401);
echo json_encode(['error' => '未授权']);
exit;
}
return $userId;
}
更安全的 JWT(推荐)
<?php
// 使用 Firebase JWT 库(composer require firebase/php-jwt)
require_once 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function createJWT($userId) {
$payload = [
'iss' => 'your-site.com',
'sub' => $userId,
'iat' => time(),
'exp' => time() + 3600
];
return JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256');
}
function verifyJWT($token) {
try {
$decoded = JWT::decode($token, new Key($_ENV['JWT_SECRET'], 'HS256'));
return $decoded->sub; // 返回用户ID
} catch (Exception $e) {
return false;
}
}
一次性令牌(防重放攻击)
<?php
// 生成一次性令牌
function generateOneTimeToken() {
$token = bin2hex(random_bytes(32));
$_SESSION['one_time_tokens'][$token] = time();
// 清理过期的令牌
foreach ($_SESSION['one_time_tokens'] as $t => $time) {
if (time() - $time > 300) { // 5分钟过期
unset($_SESSION['one_time_tokens'][$t]);
}
}
return $token;
}
// 验证并消耗令牌
function verifyOneTimeToken($token) {
if (isset($_SESSION['one_time_tokens'][$token])) {
unset($_SESSION['one_time_tokens'][$token]); // 立即销毁
return true;
}
return false;
}
// 用于关键操作,如密码重置链接
$resetToken = generateOneTimeToken();
$link = "https://example.com/reset?token=" . $resetToken;
安全最佳实践
使用随机数生成器
// ✅ 安全 $token = bin2hex(random_bytes(32)); // ❌ 不安全 $token = md5(uniqid(rand(), true)); // 可预测
恒定时间比较
// ✅ 安全(防时序攻击)
if (hash_equals($expected, $actual)) { /* 成功 */ }
// ❌ 不安全
if ($expected === $actual) { /* 容易被时序攻击 */ }
安全存储
// 对于 Token 存储:
// - 不要明文存数据库,存储 hash
// - 使用 password_hash() 或 hash_hmac()
$hashedToken = hash_hmac('sha256', $token, $secretKey);
// 仅存这个 hash 到数据库
完整示例(CSRF + 双提交 Cookie)
<?php
// 完整的安全表单防护类
class SecureForm {
private $secret;
public function __construct() {
$this->secret = $_ENV['CSRF_SECRET'] ?? 'change-me';
session_start();
}
public function generateToken() {
$id = session_id();
$timestamp = time();
$token = hash_hmac('sha256', $id . $timestamp, $this->secret);
setcookie('csrf_cookie', $token, 0, '/', '', true, true);
return $token;
}
public function validateToken($formToken) {
$cookieToken = $_COOKIE['csrf_cookie'] ?? '';
// 双重验证:Cookie 和 表单值都要匹配
return hash_equals($cookieToken, $formToken)
&& $this->verifyTimestamp($formToken);
}
private function verifyTimestamp($token) {
// 可以验证生成时间,比如只允许10分钟内有效
return true; // 简化处理
}
}
总结要点
| 场景 | 推荐方法 | 注意事项 |
|---|---|---|
| 表单提交 | Session + CSRF 令牌 | 每次登录后重新生成 |
| API 验证 | JWT (HS256) | 设置合理过期时间 |
| 高危操作 | 一次性令牌 | 使用后立即销毁 |
| 密码重置 | 随机短令牌 | 加时间限制 |
最重要的安全原则:
- 永远不要自创加密算法
- 使用
random_bytes()生成随机数 - 使用
hash_equals()做比较 - 令牌要有过期时间
- 敏感操作要二次验证
选择哪种方案取决于你的具体需求,但记住:安全没有银弹,需要结合实际场景综合使用。