首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Yii 框架實作 Email 信箱驗證啟用功能 (數位簽章)

     編輯:PHP 綜合知識

前言

Email 驗證是用戶註冊流程的關鍵環節,確保信箱真實有效。本文介紹使用數位簽章 (HMAC) 方式實作安全的 Email 驗證機制,避免直接暴露資料庫 ID 或使用可預測的 Token。

一、核心原理:數位簽章驗證

傳統做法儲存隨機 Token 對照表,本方案使用 HMAC-SHA256 簽署用戶資訊,驗證時重新計算比對,無需資料庫儲存 Token:

// 簽章金鑰 (需妥善保管,建議放在環境變數)
const SIGN_KEY = 'your-secret-signing-key-256-bit';

/**
 * 產生驗證連結簽章
 * @param string $email 用戶 Email
 * @param int $timestamp 過期時間戳 (可選)
 * @return string 簽章字串
 */
function generateEmailSignature($email, $timestamp = null) {
    $payload = $email . '|' . ($timestamp ?? (time() + 86400)); // 預設 24 小時有效
    return hash_hmac('sha256', $payload, SIGN_KEY);
}

/**
 * 驗證簽章
 * @param string $email
 * @param string $signature
 * @param int|null $timestamp
 * @return bool
 */
function verifyEmailSignature($email, $signature, $timestamp = null) {
    $expected = generateEmailSignature($email, $timestamp);
    return hash_equals($expected, $signature); // 等時間比對防時序攻擊
}

二、控制器實作

class AuthController extends Controller {
    // 發送驗證信
    public function actionSendVerification() {
        $email = Yii::$app->request->get('email');
        if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new BadRequestHttpException('無效的 Email');
        }
        
        // 檢查用戶是否存在
        $user = User::find()->where(['email' => $email])->one();
        if (!$user) {
            throw new NotFoundHttpException('用戶不存在');
        }
        
        if ($user->status === User::STATUS_ACTIVE) {
            return $this->render('message', ['msg' => '帳號已啟用,請直接登入']);
        }
        
        // 產生簽章與連結
        $expireTime = time() + 86400; // 24 小時
        $signature = generateEmailSignature($email, $expireTime);
        
        $verifyUrl = Url::to(['auth/verify-email', 
            'email' => $email, 
            'sig' => $signature, 
            'exp' => $expireTime
        ], true); // 產生絕對網址
        
        // 發送郵件
        $mail = Yii::$app->mailer->compose('verifyEmail', [
            'user' => $user,
            'verifyUrl' => $verifyUrl,
            'expireHours' => 24
        ]);
        $mail->setTo($email)
             ->setSubject('請驗證您的 Email 啟用帳號')
             ->send();
        
        return $this->render('message', ['msg' => '驗證信已發送,請至信箱查收']);
    }
    
    // 驗證連結點擊處理
    public function actionVerifyEmail() {
        $email = Yii::$app->request->get('email');
        $signature = Yii::$app->request->get('sig');
        $expire = (int) Yii::$app->request->get('exp');
        
        // 參數檢查
        if (!$email || !$signature || !$expire) {
            throw new BadRequestHttpException('驗證連結參數不完整');
        }
        
        // 檢查過期
        if (time() > $expire) {
            return $this->render('message', [
                'msg' => '驗證連結已過期 (24小時有效),請重新申請',
                'type' => 'error'
            ]);
        }
        
        // 驗證簽章
        if (!verifyEmailSignature($email, $signature, $expire)) {
            return $this->render('message', [
                'msg' => '驗證連結無效或已被竄改',
                'type' => 'error'
            ]);
        }
        
        // 啟用帳號
        $user = User::find()->where(['email' => $email])->one();
        if (!$user) {
            return $this->render('message', [
                'msg' => '用戶不存在',
                'type' => 'error'
            ]);
        }
        
        if ($user->status === User::STATUS_ACTIVE) {
            return $this->render('message', ['msg' => '帳號已啟用,請直接登入']);
        }
        
        $user->status = User::STATUS_ACTIVE;
        $user->email_verified_at = date('Y-m-d H:i:s');
        $user->save(false); // 跳過驗證直接儲存
        
        return $this->render('message', [
            'msg' => '帳號啟用成功!請登入',
            'type' => 'success'
        ]);
    }
}

三、郵件視圖模板

// views/mail/verifyEmail.php
<?php
use yii\helpers\Html;
?>
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
    <h2 style="color: #333;">歡迎加入我們!</h2>
    <p>親愛的 <?= Html::encode($user->username) ?>,</p>
    <p>感謝您註冊,請點擊下方按鈕驗證您的 Email 地址:</p>
    <div style="text-align: center; margin: 30px 0;">
        <a href="<?= $verifyUrl ?>" 
           style="background: #007bff; color: white; padding: 12px 30px; 
                  text-decoration: none; border-radius: 4px; display: inline-block;">
            驗證我的 Email
        </a>
    </div>
    <p style="color: #666; font-size: 0.9rem;">
        此連結將在 <?= $expireHours ?> 小時後失效。若非您本人操作,請忽略此信。
    </p>
    <hr>
    <p style="color: #999; font-size: 0.8rem;">
        如果按鈕無法點擊,請複製以下連結至瀏覽器:<br>
        <a href="<?= $verifyUrl ?>"><?= $verifyUrl ?></a>
    </p>
</div>

四、資料庫欄位設計

CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `username` VARCHAR(50) NOT NULL,
    `email` VARCHAR(100) NOT NULL UNIQUE,
    `password_hash` VARCHAR(255) NOT NULL,
    `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=未驗證, 10=啟用',
    `email_verified_at` DATETIME NULL,
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

五、安全性優勢分析

威脅傳統 Token 做法數位簽章做法
Token 洩露資料庫可被讀取無 Token 儲存,無洩露風險
Token 猜測需足夠熵值HMAC-SHA256 不可逆推導
重放攻擊需單獨失效機制內建過期時間,自動失效
資料庫擴充需額外表格零儲存成本
時序攻擊字串比較風險hash_equals() 等時間比對

六、進階:支援多種驗證類型

// 通用簽章服務
class SignatureService {
    private $key;
    
    public function __construct($key = null) {
        $this->key = $key ?? Yii::$app->params['signature.key'];
    }
    
    // 產生帶用途的簽章
    public function sign($identifier, $purpose, $expireHours = 24, $extraData = []) {
        $expire = time() + $expireHours * 3600;
        $payload = [
            'id' => $identifier,
            'purpose' => $purpose,  // email_verify, password_reset, email_change
            'exp' => $expire,
            'data' => $extraData
        ];
        $payloadStr = base64_encode(json_encode($payload));
        $sig = hash_hmac('sha256', $payloadStr, $this->key);
        return $payloadStr . '.' . $sig;
    }
    
    // 驗證並解析
    public function verify($token) {
        [$payloadStr, $sig] = explode('.', $token, 2);
        if (!hash_equals(hash_hmac('sha256', $payloadStr, $this->key), $sig)) {
            return false;
        }
        $payload = json_decode(base64_decode($payloadStr), true);
        if (time() > $payload['exp']) return false;
        return $payload;
    }
}

// 使用範例
$service = new SignatureService();
// 產生密碼重設連結
$resetToken = $service->sign($user->id, 'password_reset', 2, ['email' => $user->email]);
// 驗證
$payload = $service->verify($resetToken);
if ($payload && $payload['purpose'] === 'password_reset') {
    // 允許重設密碼
}

結語

數位簽章方式實作 Email 驗證具有無狀態、防竄改、自帶過期、零儲存等優勢,特別適合微服務架構或不想維護 Token 資料表的專案。關鍵在於妥善保管簽章金鑰,建議使用環境變數或密鑰管理系統,並定期輪換金鑰。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved