使用 Google Cloud Database Migration Service(DMS)完成数据复制后,真正的切换工作并不一定结束。数据库架构和事务数据已经同步,应用却可能在连接 Cloud SQL for SQL Server 时收到 Msg 18456: Login failed for user。
常见原因是:DMS 迁移了数据库级对象和数据,但没有迁移源 SQL Server 实例级的登录名、密码哈希以及权限。解决问题的关键,不是重新猜测密码或批量手工建用户,而是让目标实例中的登录名拥有与源实例一致的 SID,并正确映射到已迁移的数据库用户。
DMS 为什么不迁移登录名
SQL Server 的安全对象分布在不同层级:
- Login 位于实例级,通常存储在
master数据库中,负责验证客户端能否连接 SQL Server。 - User 位于具体业务数据库中,负责决定登录连接后能执行哪些操作。
- SID 是连接 Login 和 User 的安全标识符。数据库用户会随数据库备份、还原或 DMS 复制一起迁移,但实例级 Login 不会自动出现在目标实例中。
DMS 不复制 master 数据库、服务器登录名和服务器级权限,是一种有意的安全边界,而不是单纯的功能缺口。
源环境和 Cloud SQL 运行在不同的管理模型下。一个源实例中拥有 sysadmin 权限的账号,不应该因为复制系统数据库,就自动获得托管云数据库中的同等权限。与此同时,自动复制密码哈希和服务器级安全凭据,也可能绕过企业对 PCI-DSS、SOC 2 等合规场景的审批和审计要求。
迁移过程还提供了清理旧凭据的机会。源实例中经常存在多年未使用的 SQL 登录名,直接全部复制到云端会扩大攻击面。对于适合企业身份体系的环境,可以进一步考虑使用 Customer-Managed Active Directory(CMAD)等集中式认证方案,逐步减少对传统 SQL 身份验证的依赖。
Login、User 与 SID 的关系
假设源实例中存在登录名 app_user,业务数据库中也存在同名用户:
客户端连接
|
v
Login: app_user -- 实例级认证
|
| 通过 SID 映射
v
User: app_user -- CustomerDB 中的数据库级授权
迁移 CustomerDB 后,数据库用户及其 SID 仍然存在。如果 Cloud SQL 的 master 数据库中没有对应的 app_user Login,应用无法完成实例级认证。如果目标端手工创建了同名 Login,但生成了不同的 SID,登录可以通过服务器认证,却无法自动对应到数据库用户,最终形成 orphaned user。
因此,迁移登录名时需要同时保留:
- 原始登录名
- 原始密码哈希
- 原始 SID
- 默认数据库等必要属性
用 sp_help_revlogin 生成登录迁移脚本
Microsoft 提供的 sp_help_revlogin 是迁移 SQL Server 登录名的经典工具。配合 sp_hexadecimal 使用时,它可以为源实例中的 SQL Server 身份验证登录名生成 CREATE LOGIN 语句,并包含原始的加密密码哈希和 SID。
1. 在源实例创建辅助存储过程
使用 SSMS 连接源 SQL Server,并在源实例的 master 数据库中执行 Microsoft 官方最新版脚本,创建以下两个过程:
master.dbo.sp_hexadecimalmaster.dbo.sp_help_revlogin
这里不直接复制完整的 Microsoft 辅助脚本,因为脚本版本和 SQL Server 版本应以官方文档中的最新版为准。执行前确认脚本来自可信来源,并在变更流程中审查其内容。
2. 生成登录名脚本
在 SSMS 中将查询结果切换为 Results to Text,然后执行:
USE [master];
GO
EXEC master.dbo.sp_help_revlogin;
GO
过程会输出一组可以在目标实例执行的 T-SQL,形式类似于:
CREATE LOGIN [app_user]
WITH PASSWORD = 0x01004F3D... HASHED,
SID = 0x8D2F...,
DEFAULT_DATABASE = [CustomerDB];
GO
实际输出中的密码哈希和 SID 必须完整保留。不要手工截断、改写或将哈希转换成明文密码。建议把生成结果作为受控的迁移工件保存,并限制访问权限,因为它包含可用于恢复登录认证状态的敏感凭据材料。
3. 在 Cloud SQL 执行生成结果
确认目标 Cloud SQL for SQL Server 实例已经准备好,并且执行账号具备创建登录名所需的权限。将审核后的生成脚本连接到目标实例执行:
USE [master];
GO
CREATE LOGIN [app_user]
WITH PASSWORD = 0x01004F3D... HASHED,
SID = 0x8D2F...,
DEFAULT_DATABASE = [CustomerDB];
GO
上面的哈希和 SID 只是格式示例,不能直接用于生产环境。实际运行时必须使用 sp_help_revlogin 针对源实例生成的完整语句。
如果源数据库中的用户 SID 与目标 Login SID 一致,数据库用户就能自动映射到新实例中的登录名,应用通常无需修改密码即可完成切换。执行前后仍应根据 Cloud SQL 当前支持的权限模型和实例配置验证语句兼容性。
已经出现 orphaned user 怎么处理
如果团队在执行 sp_help_revlogin 之前,已经在目标端手工创建了同名登录名,目标 Login 很可能拥有不同的 SID。此时可以在对应的业务数据库中重新绑定用户和登录名:
USE [CustomerDB];
GO
ALTER USER [app_user] WITH LOGIN = [app_user];
GO
这条语句会更新数据库用户与服务器登录名之间的 SID 映射。执行后可以用以下查询确认用户和登录名的 SID 是否一致:
USE [CustomerDB];
GO
SELECT
dp.name AS database_user,
dp.sid AS database_user_sid,
sp.name AS server_login,
sp.sid AS server_login_sid
FROM sys.database_principals AS dp
LEFT JOIN master.sys.server_principals AS sp
ON sp.sid = dp.sid
WHERE dp.name = N'app_user';
GO
如果查询不到对应的 server_login,说明目标实例仍缺少登录名,或者登录名的 SID 尚未正确修复。不要只根据用户名判断映射是否正确,SID 才是 SQL Server 识别两者关系的关键。
切换前的检查清单
可以把登录迁移放在正式 cutover 前的演练阶段完成,并逐项验证:
- 源实例中只筛选仍在使用、且经过审批的 SQL 登录名。
sp_help_revlogin使用 Microsoft 官方最新版脚本创建。- 生成的密码哈希和 SID 未被截断或格式化工具改写。
- 目标 Cloud SQL 实例中的执行账号拥有所需权限。
- 业务数据库用户与目标 Login 的 SID 已核对。
- 应用连接池、默认数据库和最小权限配置已验证。
- 迁移工件按密码凭据处理,限制存储、传输和日志暴露范围。
- 对不再使用的旧登录名不进行迁移,或在迁移后立即禁用并清理。
迁移不是简单复制凭据
对于 lift-and-shift 项目,使用 sp_help_revlogin 保留密码哈希和 SID,是恢复应用连接、避免 orphaned user 的直接路径。但它也意味着源环境中的认证方式被延续到了云端。更长期的方案是结合 CMAD 等企业身份集成,集中管理账号生命周期、认证策略和审计记录。
因此,实际项目可以分两步推进:短期使用受控脚本完成无感切换,保证应用按时恢复;长期清理遗留 SQL 登录名,迁移到更适合云端治理的集中身份体系。DMS 负责数据库数据复制,登录名迁移则应作为独立、可审计的安全变更来设计。