不同 CMS 之间迁移用户,密码的正确做法只有两条路:目标站能识别源站的哈希算法,就直接搬密码字段、登录时惰性重算;识别不了,就老老实实走全量密码重置。权限则必须先做「源角色 → 目标角色」映射表再导数据,直接搬 group id 一定会出乱子。
密码能不能直接搬过去?
结论:只有当源站和目标站的哈希算法、盐存储方式、cost 参数三者完全一致时,密码字段才能原样复制,否则必须加兼容层或强制重置。
判断兼容性看密码字段的前缀就够了:$2y$10$ 开头是 bcrypt(60 字符),$argon2id$ 开头是 Argon2id,$P$B 或 $P$H 开头是 WordPress 用的 phpass(旧式 34 字符),纯 32 位十六进制基本是裸 MD5,* 加 40 位是 MySQL PASSWORD() 的旧格式。Discuz、phpBB、WordPress 三家的默认存储互不相同,直接复制过去的结果是全员登录失败,而且用户会以为「账号被清空了」。
另一个常见误区是「把源站盐值一起带过去就行」。盐值如果和用户名、注册时间做了拼接(部分老系统是 md5(md5(pass)+salt) 甚至 md5(pass+username)),只搬密码字段而不搬那条拼接规则,等于没搬。
密码迁移的三种方案怎么选?
结论:用户量小于 5000 且能触达邮箱,优先强制重置;用户量大、又不能接受大批流失,就用旧哈希兼容层;双哈希包裹只适合临时过渡,不建议长期保留。
- 直接搬字段:算法完全一致时用,成本最低,一条
UPDATE 就完事,迁移后无需任何额外代码。适合同源 CMS 换版本、或从分支版迁回官方版。
- 兼容层 + 惰性重算(lazy rehash):用户表加两个字段,
legacy_hash varchar(255) 和 legacy_algo varchar(16),把旧哈希整段塞进 legacy_hash,password 置空。登录流程改成:password 为空时校验 legacy_hash,通过后立刻用新算法(推荐 bcrypt cost=12 或 Argon2id)重算写入 password 并清空 legacy_hash。用户全程无感,跑几个月后 legacy_hash 自然清空,再把字段删掉。
- 强制重置:给每个用户生成 32 字节随机 token,存
reset_token 和 reset_expires,邮件链接有效期设 24 到 72 小时,过期作废。这是唯一 100% 安全的做法,代价是老用户可能有一批彻底流失。
无论选哪种,密码字段长度都要给到 varchar(255),因为 Argon2id 的输出长度远超 bcrypt 的 60 字符,字段开成 char(32) 是迁移后最常见的事故。
旧哈希兼容登录具体怎么写?
结论:核心是把「校验」和「存储」拆成两步,校验走旧规则,成功后立即用新规则落库。
以 PHP 为例,登录逻辑大致是:
① 按用户名或邮箱取出 password 与 legacy_hash;
② password 非空 → 用 password_verify($input, $password) 校验;
③ password 为空且 legacy_hash 非空 → 按 legacy_algo 分支走旧规则(如 md5(md5($input).$salt)),匹配成功则 password_hash($input, PASSWORD_BCRYPT, ['cost'=>12]) 后写回,并清空 legacy_hash;
④ 两者都为空 → 走重置密码流程,不要允许空密码登录。
注意点有两个:一是重算必须放在登录成功之后、且用事务包住,避免并发下重复写;二是迁移期间禁止关闭旧算法分支,等 legacy_hash 非空记录数降到 0 再删代码。
权限怎么迁?为什么不能直接搬 group id?
结论:权限迁移是「映射」不是「复制」,必须先在 Excel 或 CSV 里列清源站每个用户组对应的目标角色,再按映射导数据。
标准 RBAC 结构是三张表:users、roles、permissions,加一张 user_roles 关联表。迁移步骤:
- 导出源站全部用户组清单(含
groupid、名称、颜色、是否管理组),这一步最容易漏掉「管理组」和「用户组」在部分 CMS 里是两套体系(Discuz 的 adminid 与 groupid 就是分开的)。
- 建映射表
source_group_id / source_name / target_role_id,逐行人工确认,特别是「版主」「超级版主」「游客」这三类,映射错了要么权限过大要么全站只读。
- 用映射表批量写入
user_roles,不要用 INSERT ... SELECT 直接拷 ID。
- 单独处理版块级权限:版主权限通常是「用户 + 版块」二维的,目标站如果没有版块级角色,就得拆成本站对应的管理组,或者退化为单版块的审核员。
- 保留
old_uid → new_uid 映射表,帖子表、附件表、评分表的作者 ID 都要靠它转换,这张表建议永久留存。
迁移完成后必须核对的 5 个点
结论:用户表迁完不等于迁移结束,登录、发帖、计数三类问题要逐项验证。
- 邮箱唯一性与白名单:目标站如果限制了注册邮箱域名(如只允许
qq.com),老用户里其他域名的账号要么改绑、要么迁移时直接标记为待验证状态,否则会出现「能登录却改不了密码」。
- 重名与大小写:MySQL 的
utf8mb4_unicode_ci 不区分大小写,Admin 和 admin 会被判为重复,导入前必须跑一遍重名检测脚本。
- 发帖权限依赖项:有的站点要求上传头像后才能发帖,迁移过来的老用户如果头像字段没迁,会集体提示「无权限发帖」,看起来像权限迁移失败,其实是头像表没导。
- 计数类字段要重算不要照搬:发帖数、积分、经验、等级门槛在不同系统里定义完全不同(例如等级门槛可能是 100/200/500/1500 经验四档),照搬旧数值会产生大批异常等级,正确做法是按新站规则从内容表重新
COUNT 计算。
- 冷却与审核策略:新站若开启了发帖冷却(如 30 秒)或先审后发,迁移后第一波用户集中登录发帖会被大量拦截,建议迁移完成当天临时放宽,稳定后再恢复。
迁移用户的本质是「保住登录态」和「保住权限边界」两件事:密码按哈希兼容性分方案处理,能识别就惰性重算、不能识别就重置;权限按映射表逐个角色对应,绝不直接搬 ID。做完这两步再核对邮箱、重名、头像、计数,才算真正迁完。