PHP 怎么生成一次性链接

wen PHP项目 3

本文目录导读:

PHP 怎么生成一次性链接

  1. 生成一次性Token的基本方法
  2. 完整实现示例
  3. 增强安全性
  4. 数据库清理
  5. 注意事项

在 PHP 中生成一次性链接(一次性Token)是一种常见的做法,主要用于邮箱验证、密码重置、邀请链接等场景,以下是几种实现方式:

生成一次性Token的基本方法

<?php
// 方法1:使用 random_bytes(推荐)
$token = bin2hex(random_bytes(32));
// 方法2:使用 hash_hmac
$token = hash_hmac('sha256', uniqid(mt_rand(), true), 'your-secret-key');
// 方法3:使用 openssl(如果可用)
$token = bin2hex(openssl_random_pseudo_bytes(32));
?>

完整实现示例

数据库表设计

CREATE TABLE one_time_tokens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    token VARCHAR(64) NOT NULL UNIQUE,
    user_id INT NOT NULL,
    type ENUM('email_verify', 'password_reset', 'invitation') NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at DATETIME NOT NULL,
    used_at DATETIME DEFAULT NULL,
    INDEX idx_token (token)
);

生成一次性链接的类

<?php
class OneTimeLink {
    private $pdo;
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    /**
     * 生成一次性链接
     * @param int $userId 用户ID
     * @param string $type 链接类型
     * @param int $expireMinutes 过期时间(分钟)
     * @return array 返回链接和Token
     */
    public function generate($userId, $type, $expireMinutes = 60) {
        // 生成Token
        $token = bin2hex(random_bytes(32));
        // 计算过期时间
        $expiresAt = date('Y-m-d H:i:s', strtotime("+{$expireMinutes} minutes"));
        // 保存到数据库
        $stmt = $this->pdo->prepare(
            "INSERT INTO one_time_tokens (token, user_id, type, expires_at) 
             VALUES (?, ?, ?, ?)"
        );
        $stmt->execute([$token, $userId, $type, $expiresAt]);
        // 构建链接
        $link = "https://yourdomain.com/verify.php?token=$token";
        return [
            'token' => $token,
            'link' => $link,
            'expires_at' => $expiresAt
        ];
    }
    /**
     * 验证并返回Token信息
     * @param string $token 要验证的Token
     * @return array|null 返回Token信息或null
     */
    public function validate($token) {
        // 查询Token
        $stmt = $this->pdo->prepare(
            "SELECT * FROM one_time_tokens 
             WHERE token = ? AND used_at IS NULL AND expires_at > NOW()"
        );
        $stmt->execute([$token]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result ?: null;
    }
    /**
     * 使用Token(标记已使用)
     * @param string $token Token值
     * @return bool
     */
    public function consume($token) {
        $stmt = $this->pdo->prepare(
            "UPDATE one_time_tokens 
             SET used_at = NOW() 
             WHERE token = ? AND used_at IS NULL"
        );
        return $stmt->execute([$token]);
    }
}
?>

使用示例

<?php
// 1. 生成链接
$linkGenerator = new OneTimeLink($pdo);
$result = $linkGenerator->generate(
    userId: 123,
    type: 'email_verify',
    expireMinutes: 30
);
// 发送邮件
$emailContent = "请点击以下链接完成邮箱验证(30分钟内有效):\n" . $result['link'];
sendEmail($userEmail, "邮箱验证", $emailContent);
// 2. 验证并处理请求
if (isset($_GET['token'])) {
    $token = $_GET['token'];
    // 验证Token
    $tokenInfo = $linkGenerator->validate($token);
    if ($tokenInfo) {
        // Token有效,处理业务逻辑
        $userId = $tokenInfo['user_id'];
        // 更新用户状态
        updateUserVerified($userId);
        // 使用Token(一次性)
        $linkGenerator->consume($token);
        echo "验证成功!";
    } else {
        echo "链接无效或已过期";
    }
}
?>

增强安全性

使用JWT(JSON Web Token)

<?php
require_once 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JWTOneTimeToken {
    private $secretKey;
    public function __construct($secretKey) {
        $this->secretKey = $secretKey;
    }
    public function generate($userId, $expireMinutes = 60) {
        $issuedAt = time();
        $expiration = $issuedAt + $expireMinutes * 60;
        $payload = [
            'user_id' => $userId,
            'iat' => $issuedAt,
            'exp' => $expiration,
            'jti' => bin2hex(random_bytes(16))
        ];
        return JWT::encode($payload, $this->secretKey, 'HS256');
    }
    public function validate($token) {
        try {
            $decoded = JWT::decode($token, new Key($this->secretKey, 'HS256'));
            return (array)$decoded;
        } catch (Exception $e) {
            return null;
        }
    }
}
?>

带签名的一次性链接

<?php
class SignedOneTimeLink {
    private $secretKey;
    public function generate($userId, $action, $expireMinutes = 60) {
        $timestamp = time();
        $expiry = $timestamp + ($expireMinutes * 60);
        // 构建数据
        $data = "$userId|$action|$expiry";
        // 生成签名
        $signature = hash_hmac('sha256', $data, $this->secretKey);
        // 构建链接
        $url = "https://yourdomain.com/handle.php";
        $params = http_build_query([
            'user' => $userId,
            'action' => $action,
            'exp' => $expiry,
            'sig' => $signature
        ]);
        return "$url?$params";
    }
    public function validate($params) {
        $signature = $params['sig'] ?? '';
        unset($params['sig']);
        // 重建数据
        $data = implode('|', [
            $params['user'],
            $params['action'],
            $params['exp']
        ]);
        // 验证签名
        $expected = hash_hmac('sha256', $data, $this->secretKey);
        // 检查签名和过期时间
        if (hash_equals($expected, $signature) && time() < $params['exp']) {
            return true;
        }
        return false;
    }
}
?>

数据库清理

定期清理过期的一次性链接:

<?php
// 删除过期的记录
function cleanupExpiredTokens($pdo) {
    $stmt = $pdo->prepare(
        "DELETE FROM one_time_tokens 
         WHERE expires_at < NOW() OR 
         (used_at IS NOT NULL AND used_at < DATE_SUB(NOW(), INTERVAL 24 HOUR))"
    );
    $stmt->execute();
}
// 定时任务:每天执行一次
cleanupExpiredTokens($pdo);
?>

注意事项

  1. Token长度:建议至少32字节(64个十六进制字符)
  2. 过期时间:根据业务需求设置合理的过期时间
  3. 单次使用:验证后立即标记为已使用
  4. 安全性:使用HTTPS传输链接
  5. 清理机制:定期清理过期和已使用的Token

这样实现的一次性链接既有安全性,又有灵活性,可以根据具体需求进行调整。

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