Windows环境下如何持久化或复用Impersonate Token?基于JNA
实现Windows令牌的提取、持久化与复用(基于JNA)
核心思路
Windows的令牌句柄(HANDLE)是进程内的资源,无法直接序列化存储或跨进程传输。必须将其转换为序列化的令牌字节数组(可导出的二进制数据),才能持久化到数据库或通过JSON发送,之后再从字节数组还原为可用的令牌句柄。
步骤1:提取并序列化令牌
1.1 复制令牌为可导出类型
原LogonUserW返回的令牌可能没有导出权限,需要先复制出一个具备导出权限的令牌:
// 从PointerByReference获取原始令牌句柄 HANDLE hOriginalToken = new HANDLE(phToken.getValue()); PointerByReference hExportableToken = new PointerByReference(); // 调用DuplicateTokenEx创建可导出的令牌 boolean success = Advapi32.INSTANCE.DuplicateTokenEx( hOriginalToken, WinNT.TOKEN_ALL_ACCESS, null, WinNT.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, WinNT.TOKEN_TYPE.TokenPrimary, hExportableToken ); if (!success) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } HANDLE hTokenToExport = new HANDLE(hExportableToken.getValue());
1.2 获取序列化的令牌字节数组
通过GetTokenInformation的TokenSerialization类型获取序列化数据:
// 第一步:获取所需缓冲区大小 IntByReference bufferSize = new IntByReference(); Advapi32.INSTANCE.GetTokenInformation( hTokenToExport, WinNT.TOKEN_INFORMATION_CLASS.TokenSerialization, null, 0, bufferSize ); // 第二步:分配缓冲区并获取序列化数据 byte[] tokenBuffer = new byte[bufferSize.getValue()]; Pointer buffer = new Memory(bufferSize.getValue()); success = Advapi32.INSTANCE.GetTokenInformation( hTokenToExport, WinNT.TOKEN_INFORMATION_CLASS.TokenSerialization, buffer, bufferSize.getValue(), bufferSize ); if (!success) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } buffer.read(0, tokenBuffer, 0, tokenBuffer.length); // 转换为Base64字符串,方便存储和JSON传输 String serializedTokenBase64 = Base64.getEncoder().encodeToString(tokenBuffer);
1.3 关闭临时句柄
用完复制出来的令牌句柄后要关闭,避免资源泄漏:
Kernel32.INSTANCE.CloseHandle(hTokenToExport); Kernel32.INSTANCE.CloseHandle(hOriginalToken); // 如果原令牌不再使用也可以关闭
步骤2:持久化与传输
- 把
serializedTokenBase64字符串存入数据库,或直接作为JSON字段发送给其他服务(比如JSON结构{"token": "xxx..."})。 - 安全注意:序列化令牌包含敏感身份信息,存储需加密(如数据库字段加密),传输必须用HTTPS,防止泄露。
步骤3:从序列化数据还原令牌
当需要复用令牌时,将Base64字符串转回字节数组,通过LsaImportLogonUser导入为可用的令牌句柄:
// 从Base64还原字节数组 byte[] tokenBuffer = Base64.getDecoder().decode(serializedTokenBase64); // 准备LSA导入所需的参数 LSA_UNICODE_STRING originName = new LSA_UNICODE_STRING(); originName.Buffer = null; originName.Length = 0; originName.MaximumLength = 0; PointerByReference hImportedToken = new PointerByReference(); NTSTATUS status = LsaWin32.INSTANCE.LsaImportLogonUser( null, // LSA句柄,null使用默认 new LSA_STRING(), // 认证包名称,空使用默认 WinNT.SECURITY_LOGON_TYPE.Interactive, // 登录类型,和LogonUserW一致 originName, tokenBuffer, tokenBuffer.length, null, hImportedToken ); if (status.intValue() != WinNT.STATUS_SUCCESS) { throw new Win32Exception(LsaWin32.INSTANCE.LsaNtStatusToWinError(status.intValue())); } // 拿到可用的令牌句柄,可用于ImpersonateLoggedOnUser等操作 HANDLE hReusedToken = new HANDLE(hImportedToken.getValue());
关键注意事项
- 令牌有效期:Windows令牌有有效期,默认取决于域策略,过期后无法复用,需要重新登录生成新令牌。
- 权限控制:确保操作令牌的进程具备足够权限(如管理员权限或对应的身份权限),否则API调用会失败。
- 资源泄漏:所有获取的
HANDLE必须在使用完毕后调用Kernel32.INSTANCE.CloseHandle()关闭,避免系统资源耗尽。
内容的提问来源于stack exchange,提问作者Ravi Soni
相关产品推荐
相关产品推荐

