
年初接手了一个内部管理系统,技术栈是 PHP 5.6 + jQuery,没有框架,没有 ORM,用户认证直接调 MySQL。当我看到数据库 users 表里明晃晃存着 password 字段,值是用户注册时填的原始密码时,后背一凉——这个系统已经跑了四年,有两百多个企业客户在用。
更头疼的是认证逻辑散落在至少 7 个文件里:
login.php 处理用户名密码校验check_auth.php 在每个页面顶部 include 做 session 检查logout.php 清理 sessionreset_password.php 管理员直接改数据库api/auth.php 给几个简单的 API 做 token 校验(token 也是明文存在表里)if ($_SESSION['user_id'] == '') 这种硬编码判断没有任何加密、没有密码强度要求、没有登录失败限制、没有 CSRF 防护。 这个模块不仅是技术债,更是一个安全定时炸弹。
重构的目标很明确:用 bcrypt 存储密码、引入 JWT 做无状态认证、统一认证入口、加基础防护。但老系统没有测试,贸然重写万一漏了某个边缘逻辑就会影响线上业务。我需要先彻底理解现有逻辑,再设计安全方案,最后渐进式迁移。这个过程中 AI 工具帮了大忙。
传统做法是手动翻 7 个文件,画流程图。这次我把所有相关代码文件拖进 Cursor,用它的项目级索引功能,然后问:
Cursor 基于 Claude Sonnet 4 模型,在几十秒内给出了分析报告。关键发现:
$_SESSION['user_id'],有的检查 $_SESSION['login'],还有一个页面自己维护了一套 cookie 校验。md5(username + 'secret'),可以被预测。这一步帮我省了至少半天的人工梳理时间,而且 AI 不会遗漏分散在边角的逻辑——有个 download_report.php 里藏了一句独立的 session 检查,人工排查大概率会漏掉。
搞清现状后,我让 Cursor 帮我设计目标方案:
AI 给出的方案包含:
users 表新增 password_hash、login_attempts、locked_until 字段,保留旧的 password 字段作为过渡。Authorization header。我对方案做了一处重要修改:AI 建议直接废弃旧 password 字段,我坚持保留作为过渡期兼容——因为还有其他微服务直接读这个字段。这是 AI 不会知道的业务上下文,人的判断仍然不可或缺。
新旧认证方式需要共存一段时间,既要让新登录逻辑生效,又要保证老 session 和 API 继续工作。我设计了一个双轨运行期:
// 认证适配器(简化版)
class AuthAdapter {
// 新用户或已迁移用户走 JWT
public function authenticate($username, $password) {
$user = $this->db->getUser($username);
// 优先用 bcrypt 验证已迁移的密码
if (!empty($user['password_hash'])) {
if (password_verify($password, $user['password_hash'])) {
return $this->issueJwt($user);
}
}
// 回退到明文验证(仅过渡期)
if ($password === $user['password']) {
// 验证成功后立即迁移密码
$this->migratePassword($user['id'], $password);
return $this->issueJwt($user);
}
// 记录失败次数
$this->recordFailedAttempt($user['id']);
return false;
}
// 迁移密码
private function migratePassword($userId, $plainPassword) {
$hash = password_hash($plainPassword, PASSWORD_BCRYPT, ['cost' => 12]);
$this->db->update($userId, [
'password_hash' => $hash,
'password' => null // 清除明文
]);
}
}
这样每个用户首次用明文密码登录后,密码立即被迁移为 bcrypt 并删除明文。整个过程对用户透明。
AI 在这个阶段的作用:我让 Cursor 帮我找到所有直接读取 password 字段的 SQL 语句和代码(共 11 处),确保迁移逻辑不会漏掉任何读操作。这个搜索人工做容易漏,AI 几秒就扫完了。
迁移完成后,AI 帮我做了两件事:
AI 生成了 30 多个测试用例,我手动补充了 3 个边缘场景(如并发登录失败计数),全部通过。
AI 帮我生成了 Nginx 和 PHP 的安全头配置:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
整个迁移在两周内完成,期间系统正常运行,没有用户反馈登录问题。
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 密码存储方式 | 明文 | bcrypt (cost 12) |
| 认证文件数 | 7 个 | 1 个中间件 + 1 个 service |
| 登录失败限制 | 无 | 5 次/30 分钟 |
| 密码重置流程 | 管理员直接改库 | 邮箱验证链接 |
| API 认证 | 固定 token (MD5) | JWT (15 分钟过期) |
| 安全测试覆盖 | 0% | 87% |
最重要的是,这次重构让我对遗留系统的安全升级有了一套可复用的方法论:AI 做分析和方案设计,人做关键决策和验证,渐进式迁移不中断业务。
download_report.php 里的独立 session 检查,AI 帮我一网打尽。如果你也在维护遗留系统,建议花半天时间让 AI 帮你做一次安全审计,你会发现自己一直不敢动的代码其实没那么可怕。