PHP 怎么PHP令牌安全

wen PHP项目 2

本文目录导读:

PHP 怎么PHP令牌安全

  1. CSRF 令牌(表单安全)
  2. API 令牌(身份验证)
  3. 一次性令牌(防重放攻击)
  4. 安全最佳实践
  5. 总结要点

在 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) 设置合理过期时间
高危操作 一次性令牌 使用后立即销毁
密码重置 随机短令牌 加时间限制

最重要的安全原则:

  1. 永远不要自创加密算法
  2. 使用 random_bytes() 生成随机数
  3. 使用 hash_equals() 做比较
  4. 令牌要有过期时间
  5. 敏感操作要二次验证

选择哪种方案取决于你的具体需求,但记住:安全没有银弹,需要结合实际场景综合使用。

抱歉,评论功能暂时关闭!