Passport 是一款为 Typecho 博客系统设计的安全插件。
插件提供安全的密码找回与重置功能。插件内置零配置图片验证码,同时支持多种主流第三方人机验证方式。内置严格的 Token 管理、HMAC 签名机制以及支持 CDN 环境的高级防爆破功能。本项目遵循 GPLv2 协议,完全免费开源。


Passport 是为 Typecho 博客系统开发的专业密码找回插件,提供安全可靠的密码重置功能,集成多种人机验证机制,支持防暴力破解和 IP 速率限制。
usr/plugins/| 配置项 | 说明 | 默认值 |
|---|---|---|
| SMTP 服务器 | 邮件发送服务器地址 | smtp.example.com |
| SMTP 端口 | 邮件发送服务器端口 | 465 |
| SMTP 帐号 | 邮件发送账号 | noreply@example.com |
| SMTP 密码 | 邮件发送密码 | - |
| 加密类型 | SMTP 加密方式 | ssl |
| 验证码类型 | 人机验证方式 | 内置图片验证码 |
| HMAC 签名密钥 | 用于签名验证的密钥 | 自动生成 |
| 启用请求速率限制 | 防止暴力破解 | 启用 |
Passport/ ├── Plugin.php # 插件主文件,负责路由注册和配置 ├── Widget.php # 核心逻辑,处理密码重置流程 ├── Template/ # 前端模板 │ ├── forgot.php # 忘记密码页面 │ ├── reset.php # 重置密码页面 │ └── partial/ # 公共模板 │ ├── common.php # 公共变量和初始化 │ ├── header.php # HTML 头部 │ └── resource.php # 静态资源(CSS/JS) └── PHPMailer/ # 邮件发送库
Passport v1.0.2 采用独立的 Session 通知系统,完全脱离 Typecho 后台依赖:
后端实现 (Widget.php):
// 存储通知到 Session
protected function pushNotice($message, $type = 'success', $countdown = null) {
$_SESSION['passport_notice'] = [
'message' => $message,
'type' => $type,
'countdown' => $countdown
];
}
// POST-Redirect-GET 模式防止表单重复提交
$this->pushNotice('操作成功', 'success');
$this->response->redirect($redirectUrl);
前端渲染 (resource.php):
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT)translateY(-10px) → translateY(0))CSS 特性:
.passport-toast.success {
background-color: rgba(16, 185, 129, 0.1); /* 半透明绿色背景 */
border-color: #10b981;
color: #10b981; /* 主题色文本 */
}
@keyframes passkeySlideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
智能加载指示器 (v1.0.2 新增):
HTML 结构:
<div class="passport-captcha-wrapper">
<!-- 圆形加载指示器 -->
<div class="passport-captcha-loader active"></div>
<!-- 验证码图片(初始隐藏) -->
<img class="passport-captcha-img loading" onclick="refreshCaptcha(this);">
</div>
加载流程:
refreshCaptcha().loading 类,图片淡入显示,隐藏加载器JavaScript 实现:
function refreshCaptcha(imgElement) {
const wrapper = imgElement.parentElement;
const loader = wrapper.querySelector('.passport-captcha-loader');
// 显示加载器,隐藏图片
imgElement.classList.add('refreshing', 'loading');
loader.classList.add('active');
// 动态设置图片源(支持首次加载)
const baseUrl = imgElement.src || '/passport/captcha';
imgElement.src = baseUrl + '?' + Math.random();
// 加载完成:隐藏加载器,显示图片
imgElement.onload = () => {
imgElement.classList.remove('refreshing', 'loading');
loader.classList.remove('active');
};
}
CSS 动画:
/* 圆形加载器 */
.passport-captcha-loader {
width: 24px;
height: 24px;
border: 3px solid var(--passport-border);
border-top-color: var(--passport-primary);
border-radius: 50%;
animation: passkeyRotate 0.8s linear infinite;
}
/* 图片加载状态 */
.passport-captcha-img.loading {
opacity: 0; /* 加载时透明 */
pointer-events: none; /* 禁用点击 */
}
currentColor 继承主题色SHA256(UID + Timestamp + random_bytes(32))hash_hmac('sha256', $token, $secret)htmlspecialchars() 转义/passport/captchaCookie: 必须携带 Session Cookieimage/png$_SESSION['captcha']curl -X GET 'https://example.com/passport/captcha' \
-H 'Cookie: PHPSESSID=abc123...' \
--output captcha.png
/passport/forgotapplication/x-www-form-urlencoded| 字段名 | 类型 | 必填 | 验证规则 | 描述 |
|---|---|---|---|---|
| string | 是 | Email 格式 | 用户注册邮箷 | |
| captcha | string | 是 | 4-6 位字符 | 验证码(不区分大小写) |
| do | string | 是 | 固定值 mail | 操作类型标识 |
成功情况:
/passport/forgot(重定向回表单页面)失败情况:
error1. 验证请求频率(IP 速率限制) 2. 验证验证码(Session 匹配) 3. 验证邮箱格式(filter_var) 4. 查询用户是否存在 5. 生成重置 Token(SHA256) 6. 生成 HMAC 签名 7. 存储 Token 到数据库 8. 发送重置邮件(PHPMailer) 9. 存储通知到 Session 10. 重定向回表单页面(PRG 模式)
curl -X POST 'https://example.com/passport/forgot' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Cookie: PHPSESSID=abc123...' \
-d 'mail=user@example.com' \
-d 'captcha=A3B9' \
-d 'do=mail'
/passport/resetapplication/x-www-form-urlencoded| 字段名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| token | string | 是 | 64 位重置令牌(SHA256) |
| signature | string | 是 | 64 位 HMAC 签名(SHA256) |
| 字段名 | 类型 | 必填 | 验证规则 | 描述 |
|---|---|---|---|---|
| token | string | 是 | 64 位十六进制 | 重置令牌 |
| signature | string | 是 | 64 位十六进制 | HMAC 签名 |
| password | string | 是 | 6-32 位 | 新密码 |
| confirm | string | 是 | 与 password 一致 | 确认密码 |
| captcha | string | 是 | 4-6 位字符 | 验证码 |
| do | string | 是 | 固定值 password | 操作类型标识 |
成功情况:
失败情况:
/passport/reset?token=...&signature=...errorGET 请求: 1. 验证 Token 和 Signature 参数存在 2. 验证 HMAC 签名(hash_equals) 3. 查询 Token 是否存在且未使用 4. 检查 Token 是否过期(24小时) 5. 显示密码重置表单 POST 请求: 1. 重复 GET 请求的验证步骤 2. 验证验证码 3. 验证新密码格式 4. 验证两次密码一致性 5. 更新数据库中的用户密码 6. 标记 Token 为已使用 7. 存储成功通知 8. 重定向到登录页面
GET 请求(邮件链接):
https://example.com/passport/reset? token=a1b2c3d4e5f6...& signature=1a2b3c4d5e6f...
POST 请求:
curl -X POST 'https://example.com/passport/reset' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Cookie: PHPSESSID=abc123...' \
-d 'token=a1b2c3d4e5f6...' \
-d 'signature=1a2b3c4d5e6f...' \
-d 'password=NewPass123' \
-d 'confirm=NewPass123' \
-d 'captcha=X7Y9' \
-d 'do=password'
/action/passport-unblock| 字段名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| unblock_ip | string | 是 | 要解封的 IP 地址 |
| _ | string | 是 | CSRF 令牌 |
{
"success": true,
"message": "IP [192.168.1.1] 已解封。"
}
Passport 插件完美适配 BooAdmin 主题,提供一致的视觉体验。
v1.0.2 版本通知系统采用与 Passkey 插件完全一致的视觉设计:
rgba(16, 185, 129, 0.1) 用于成功通知)translateY(-10px) → translateY(0))如果您使用的是自定义主题,可以通过以下方式确保 Passport 页面与主题风格一致:
CSS 变量:Passport 使用 CSS 变量定义颜色和样式,您可以在主题中覆盖这些变量
:root {
--passport-primary: #your-color;
--passport-success: #your-success-color;
--passport-error: #your-error-color;
}
模板修改:如需深度定制,可以修改 Template/partial/resource.php 中的样式
通知系统:Passport 使用 Session 通知系统,完全独立于主题
PassportToast.show() 方法显示通知success、error、warning、info响应式断点:
@media (max-width: 480px)@media (min-width: 1920px)@media (min-width: 2560px)Passport 插件采用多层安全防护机制,确保密码重置流程的安全性:
原理:使用 HMAC-SHA256 算法对重置令牌进行签名,防止中间人攻击和链接伪造。
实现细节:
// 生成签名
$signature = hash_hmac('sha256', $token, $hmacSecret);
// 验证签名
if (!hash_equals($expectedSignature, $providedSignature)) {
throw new Exception('签名验证失败');
}
安全优势:
hash_equals() 防止时间攻击random_bytes(32) 生成,具有很高的熵值Token 生成算法:
$token = hash('sha256',
$uid . // 用户 ID
time() . // 当前时间戳
bin2hex(random_bytes(32)) // 32 字节密码学随机数
);
生命周期管理:
unusedused安全特性:
滑动窗口算法:基于 IP 地址的请求频率控制,防止暴力破解。
限制策略:
IP 获取策略(v0.1.3+):
X-Forwarded-For、X-Real-IP 等 Header内置验证码(默认):
hash_equals() 防止时序攻击Verify-and-Destroy 机制(v0.1.3+):
// 验证验证码
if (!hash_equals($sessionCode, $inputCode)) {
throw new Exception('验证码错误');
}
// 立即销毁,防止重放攻击
unset($_SESSION['captcha']);
第三方验证码支持:
Session 管理:
数据存储:
$_SESSION['captcha']$_SESSION['passport_notice'](v1.0.2)Typecho 数据库抽象层:
// 使用参数化查询
$db->select('*')
->from('table.passport_tokens')
->where('token = ?', $token)
->where('uid = ?', $uid)
->limit(1);
防护措施:
输出转义:
// HTML 输出转义
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// JavaScript 输出转义
$safeData = json_encode($data,
JSON_HEX_TAG |
JSON_HEX_AMP |
JSON_HEX_APOS |
JSON_HEX_QUOT |
JSON_UNESCAPED_UNICODE
);
防护范围:
Post-Redirect-Get 模式:防止表单重复提交和浏览器刷新时重新提交。
流程:
1. 用户提交表单(POST) ↓ 2. 后端处理请求 ↓ 3. 将结果存储到 Session ↓ 4. 重定向到结果页面(302 Redirect) ↓ 5. 浏览器发起新的 GET 请求 ↓ 6. 从 Session 读取并显示结果
安全优势:
安全 Flag 编码:防止 JSON 数据在 HTML 上下文中导致 XSS。
$json = json_encode($data,
JSON_HEX_TAG | // 转义 < 和 >
JSON_HEX_AMP | // 转义 &
JSON_HEX_APOS | // 转义 '
JSON_HEX_QUOT | // 转义 "
JSON_UNESCAPED_UNICODE // 不转义 Unicode
);
应用场景:
Typecho Security 令牌:后台操作(IP 解封、配置保存)必须携带 CSRF Token。
// 生成 Token
$token = Typecho_Widget::widget('Widget_Security')->getToken($action);
// 验证 Token
Typecho_Widget::widget('Widget_Security')->protect($action);
防护范围:
安全建议:
Passport 记录所有密码重置操作,包括:
记录所有密码重置请求,包括:
Passport 支持自定义邮件模板,您可以在后台配置中修改邮件内容。
| 变量 | 描述 |
|---|---|
| {username} | 用户名称 |
| {sitename} | 网站名称 |
| {requestTime} | 请求时间 |
| {resetLink} | 重置链接 |
| 版本 | 日期 | 主要变更 |
|---|---|---|
| 1.0.2 | 2026-03-03 | 优化通知视觉设计,改进验证码加载体验 |
| 1.0.1 | 2026-03-01 | 适配 BooAdmin 主题,优化通知系统,提升 SVG 图标质量 |
| 0.1.5 | 2025-12-01 | 增强 Token 安全性,添加密码重置历史审计功能 |
| 0.1.4 | 2025-10-15 | 新增 IP 拦截管理功能 |
| 0.1.3 | 2025-09-01 | 优化 IP 获取策略,增强验证码安全性 |
| 0.1.2-fix | 2025-08-10 | 修复安全漏洞,增强密码学安全性 |
我们欢迎社区贡献,包括:
请提交 Pull Request 或创建 Issue 来参与项目开发。
Passport 插件采用 GNU General Public License v2.0 许可证。
感谢使用 Passport 插件! 如有任何问题或建议,请随时联系我们。