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

自定义凭据提供者在UpdateRemoteCredential和SetSerialization调用后异常终止求助

自定义Windows凭据提供者问题解决方案及实现示例

问题核心

  • 调用UpdateRemoteCredential和SetSerialization后,凭据提供者进程意外终止,静态成员被重新初始化,无法留存SetSerialization获取的序列化数据
  • 原进程完成SetUserArray调用后无后续操作,随即启动新进程重新初始化所有组件;若不修改序列化的CLSID(不重定向到自定义提供者),登录流程正常,反之必现问题
  • 日志确认序列化已通过UpdateRemoteCredentials修改CLSID并传递到SetSerialization,但GetSerialization未在原进程被调用,存储的数据无法访问

关键疑问解答

ICredentialProviderFilter(包含UpdateRemoteCredential)和ICredentialProvider(包含SetSerialization)可以在同一类中实现,COM注册时只需同时注册两个接口的实现类即可。当然从调试和模块化角度,分开实现也可行,无强制要求。

C#实现示例(基于CredProvider.NET.Interop2)

核心解决思路:由于进程会重启,不能用静态成员存储数据,改用注册表临时存储实现跨进程数据传递。

using CredProvider.NET.Interop2;
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

[ComVisible(true)]
[Guid("YOUR-CUSTOM-PROVIDER-GUID")]
[ClassInterface(ClassInterfaceType.None)]
public class CustomCredProvider : ICredentialProvider, ICredentialProviderFilter
{
    // 注册表临时存储路径,用于跨进程传递序列化数据
    private const string TempRegPath = @"HKCU\Software\CustomCredProvider\Temp";
    private CREDENTIAL_PROVIDER_USAGE_SCENARIO _usageScenario;

    #region ICredentialProviderFilter 实现
    public int UpdateRemoteCredential(ref CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION pcpcsIn, ref CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION pcpcsOut)
    {
        // 拦截默认密码提供者的序列化(默认密码提供者CLSID:60b78e88-ead8-445c-9cfd-0b87f74ea6cd)
        if (pcpcsIn.clsidCredentialProvider == Guid.Parse("60b78e88-ead8-445c-9cfd-0b87f74ea6cd"))
        {
            // 复制原序列化数据,修改CLSID为当前自定义提供者的GUID
            pcpcsOut = pcpcsIn;
            pcpcsOut.clsidCredentialProvider = GetType().GUID;
            
            // 将序列化数据写入注册表,供新进程读取
            using (var key = Registry.CurrentUser.CreateSubKey(TempRegPath))
            {
                key.SetValue("SerializedData", pcpcsOut.rgbSerialization, RegistryValueKind.Binary);
                key.SetValue("CredentialType", (int)pcpcsOut.dwCredentialType);
                key.SetValue("ProviderCLSID", pcpcsOut.clsidCredentialProvider.ToString());
            }
        }
        return HRESULT.S_OK;
    }

    public int Filter(ref Guid clsidCredentialProvider, ref uint dwFlags)
    {
        // 允许所有提供者,可根据需求修改过滤逻辑
        return HRESULT.S_OK;
    }
    #endregion

    #region ICredentialProvider 实现
    public int SetUsageScenario(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, uint dwFlags)
    {
        _usageScenario = cpus;
        return HRESULT.S_OK;
    }

    public int SetSerialization(ref CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION pcpcs)
    {
        // 存储重定向后的序列化数据到注册表
        using (var key = Registry.CurrentUser.CreateSubKey(TempRegPath))
        {
            key.SetValue("SerializedData", pcpcs.rgbSerialization, RegistryValueKind.Binary);
            key.SetValue("CredentialType", (int)pcpcs.dwCredentialType);
            key.SetValue("ProviderCLSID", pcpcs.clsidCredentialProvider.ToString());
        }
        return HRESULT.S_OK;
    }

    public int GetSerialization(out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE pcpgsr, out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION pcpcs, out string pszOptionalStatusText, out CREDENTIAL_PROVIDER_STATUS_ICON pcpsiOptionalStatusIcon)
    {
        pcpgsr = CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_RETURN_CREDENTIAL_FINISHED;
        pszOptionalStatusText = string.Empty;
        pcpsiOptionalStatusIcon = CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_SUCCESS;
        pcpcs = new CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();

        // 从注册表读取序列化数据
        using (var key = Registry.CurrentUser.OpenSubKey(TempRegPath))
        {
            if (key == null) return HRESULT.E_FAIL;
            
            var serializedData = (byte[])key.GetValue("SerializedData");
            var credentialType = (uint)(int)key.GetValue("CredentialType");
            var providerClsid = Guid.Parse((string)key.GetValue("ProviderCLSID"));
            
            pcpcs.clsidCredentialProvider = providerClsid;
            pcpcs.dwCredentialType = credentialType;
            pcpcs.cbSerialization = (uint)serializedData.Length;
            pcpcs.rgbSerialization = serializedData;
            
            // 用完清理临时注册表项
            Registry.CurrentUser.DeleteSubKeyTree(TempRegPath);
            return HRESULT.S_OK;
        }
    }

    // 以下为ICredentialProvider接口的空实现,按需补充UI逻辑
    public int Advise(ICredentialProviderEvents pcpe, uint upAdviseContext) => HRESULT.S_OK;
    public int UnAdvise() => HRESULT.S_OK;
    public int GetFieldDescriptorCount(out uint pdwCount) { pdwCount = 0; return HRESULT.S_OK; }
    public int GetFieldDescriptorAt(uint dwIndex, out ICredentialProviderFieldDescriptor ppcpfd) { ppcpfd = null; return HRESULT.S_OK; }
    public int GetCredentialCount(out uint pdwCount, out uint pdwDefault, out int pbAutoLogonWithDefault) { pdwCount = 1; pdwDefault = 0; pbAutoLogonWithDefault = 0; return HRESULT.S_OK; }
    public int GetCredentialAt(uint dwIndex, out ICredentialProviderCredential ppcpc) { ppcpc = new CustomCredential(); return HRESULT.S_OK; }
    public int SetUserArray(IUnknown pcpusers) => HRESULT.S_OK;
    #endregion
}

// 自定义凭据类的基础实现,按需补充UI交互逻辑
[ComVisible(true)]
[Guid("YOUR-CREDENTIAL-GUID")]
[ClassInterface(ClassInterfaceType.None)]
public class CustomCredential : ICredentialProviderCredential
{
    // ICredentialProviderCredential接口的空实现,按需扩展
    public int Advise(ICredentialProviderCredentialEvents pcpe) => HRESULT.S_OK;
    public int UnAdvise() => HRESULT.S_OK;
    public int SetSelected(out int pbAutoLogon) { pbAutoLogon = 0; return HRESULT.S_OK; }
    public int SetDeselected() => HRESULT.S_OK;
    public int GetFieldState(uint dwFieldID, out CREDENTIAL_PROVIDER_FIELD_STATE pcpfs, out CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE pcpfis) { pcpfs = CREDENTIAL_PROVIDER_FIELD_STATE.CPFS_DISPLAY_IN_SELECTED_TILE; pcpfis = CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE.CPFIS_NONE; return HRESULT.S_OK; }
    public int GetStringValue(uint dwFieldID, out string ppsz) { ppsz = string.Empty; return HRESULT.S_OK; }
    public int GetBitmapValue(uint dwFieldID, out IntPtr phbmp) { phbmp = IntPtr.Zero; return HRESULT.S_OK; }
    public int GetCheckboxValue(uint dwFieldID, out int pbChecked, out string ppszLabel) { pbChecked = 0; ppszLabel = string.Empty; return HRESULT.S_OK; }
    public int GetComboBoxValueCount(uint dwFieldID, out uint pdwCount, out uint pdwSelectedItem) { pdwCount = 0; pdwSelectedItem = 0; return HRESULT.S_OK; }
    public int GetComboBoxValueAt(uint dwFieldID, uint dwItem, out string ppszItem) { ppszItem = string.Empty; return HRESULT.S_OK; }
    public int SetStringValue(uint dwFieldID, string psz) => HRESULT.S_OK;
    public int SetCheckboxValue(uint dwFieldID, int bChecked) => HRESULT.S_OK;
    public int SetComboBoxSelectedValue(uint dwFieldID, uint dwSelectedItem) => HRESULT.S_OK;
    public int CommandLinkClicked(uint dwFieldID) => HRESULT.S_OK;
}

注意事项

  • 替换代码中的YOUR-CUSTOM-PROVIDER-GUID和YOUR-CREDENTIAL-GUID为实际生成的GUID
  • 注册凭据提供者时,需同时注册ICredentialProvider和ICredentialProviderFilter接口,确保系统能调用到UpdateRemoteCredential
  • 避免使用进程内存储(如静态变量),必须用跨进程存储方案(注册表、共享内存等)

C++实现及C#调用方式

C++核心逻辑

和C#思路一致:实现ICredentialProvider和ICredentialProviderFilter接口,用共享内存(CreateFileMapping)存储序列化数据,编译为COM DLL后注册。

C#调用步骤

  1. 用tlbimp.exe工具将C++ DLL生成的类型库转换为.NET程序集
  2. 在C#项目中引用生成的程序集
  3. 凭据提供者通常由Windows登录进程自动加载,无需手动创建实例;若需手动调用,直接通过COM实例化即可

问题根因分析

进程重启是Windows登录流程的正常行为:当修改序列化的提供者CLSID后,系统会重启凭据提供者进程加载目标提供者,原进程的静态成员会丢失。必须使用跨进程持久化存储传递序列化数据,而非依赖进程内变量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 21:36:04