You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

断开域连接时LogonUser调用失败,如何离线验证域用户凭据?

解决方案建议

核心思路

断开VPN后LogonUser()无法验证域凭据,是因为该API默认会尝试联系域控制器做在线验证;而本地登录能成功,是Windows缓存了域用户的凭据哈希用于离线验证。要实现离线验证,需调用能直接利用本地缓存的API——LsaLogonUser,配合特定身份验证包与标志。

具体实现方案

使用LsaLogonUser结合MSV1_0_INTERACTIVE_LOGON身份验证包,并指定MSV1_0_ALLOW_WORKSTATION_LOGON标志,该标志允许使用本地缓存的域凭据完成离线验证。

代码示例(C++)

#include <windows.h>
#include <Ntsecapi.h>
#include <Schnlsp.h>

#pragma comment(lib, "advapi32.lib")

NTSTATUS ValidateCachedDomainCredentials(LPCWSTR username, LPCWSTR domain, LPCWSTR password) {
    NTSTATUS status;
    LSA_HANDLE lsaHandle;
    LSA_OBJECT_ATTRIBUTES objectAttrs = {0};
    objectAttrs.Length = sizeof(LSA_OBJECT_ATTRIBUTES);

    // 打开LSA连接
    status = LsaOpenPolicy(NULL, &objectAttrs, POLICY_ALL_ACCESS, &lsaHandle);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 构造交互式登录结构体
    MSV1_0_INTERACTIVE_LOGON logonInfo = {0};
    logonInfo.MessageType = MsV1_0InteractiveLogon;
    wcscpy_s(logonInfo.LogonDomainName.Buffer, domain);
    logonInfo.LogonDomainName.Length = (USHORT)(wcslen(domain) * sizeof(WCHAR));
    logonInfo.LogonDomainName.MaximumLength = (USHORT)(sizeof(logonInfo.LogonDomainName.Buffer));
    
    wcscpy_s(logonInfo.UserName.Buffer, username);
    logonInfo.UserName.Length = (USHORT)(wcslen(username) * sizeof(WCHAR));
    logonInfo.UserName.MaximumLength = (USHORT)(sizeof(logonInfo.UserName.Buffer));
    
    wcscpy_s(logonInfo.Password.Buffer, password);
    logonInfo.Password.Length = (USHORT)(wcslen(password) * sizeof(WCHAR));
    logonInfo.Password.MaximumLength = (USHORT)(sizeof(logonInfo.Password.Buffer));

    // 获取身份验证包ID
    ULONG authPackage;
    status = LsaLookupAuthenticationPackage(lsaHandle, MSV1_0_PACKAGE_NAME, &authPackage);
    if (!NT_SUCCESS(status)) {
        LsaClose(lsaHandle);
        return status;
    }

    LSA_STRING originName = {0};
    LSA_TOKEN_INFORMATION_TYPE tokenType = TokenPrimary;
    HANDLE tokenHandle = NULL;
    PVOID profileBuffer = NULL;
    ULONG profileBufferLength = 0;
    LUID logonId = {0};
    SECURITY_LOGON_TYPE logonType = Interactive;
    
    // 关键:启用离线缓存验证标志
    logonInfo.Flags = MSV1_0_ALLOW_WORKSTATION_LOGON;

    status = LsaLogonUser(
        lsaHandle,
        &originName,
        logonType,
        authPackage,
        &logonInfo,
        sizeof(logonInfo),
        NULL,
        NULL,
        &profileBuffer,
        &profileBufferLength,
        &logonId,
        &tokenHandle
    );

    // 清理资源
    if (tokenHandle != NULL) {
        CloseHandle(tokenHandle);
    }
    if (profileBuffer != NULL) {
        LsaFreeReturnBuffer(profileBuffer);
    }
    LsaClose(lsaHandle);

    return status;
}

关键说明

  • 权限要求:调用LsaLogonUser需要进程拥有SE_TCB_NAME权限,或以管理员身份运行程序。
  • 缓存前提:用户必须曾成功通过该域账户登录过本地计算机,Windows才会缓存凭据哈希;若从未登录过,离线验证仍会失败。
  • 错误处理:可通过LsaNtStatusToWinError()将NTSTATUS转换为Windows错误码,方便排查问题。

关于LOGON32_LOGON_NEW_CREDENTIALS的说明

该标志的设计目的是创建新的凭据上下文,而非验证凭据有效性——它不会检查用户名和密码的正确性,因此无论输入什么都会返回成功,不能用于身份验证场景。

内容的提问来源于stack exchange,提问作者Joseph Willcoxson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:55:19