自定义凭据提供者在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#调用步骤
- 用
tlbimp.exe工具将C++ DLL生成的类型库转换为.NET程序集 - 在C#项目中引用生成的程序集
- 凭据提供者通常由Windows登录进程自动加载,无需手动创建实例;若需手动调用,直接通过COM实例化即可
问题根因分析
进程重启是Windows登录流程的正常行为:当修改序列化的提供者CLSID后,系统会重启凭据提供者进程加载目标提供者,原进程的静态成员会丢失。必须使用跨进程持久化存储传递序列化数据,而非依赖进程内变量。
内容的提问来源于stack exchange,提问作者smhh22
相关产品推荐
相关产品推荐

