CredentialProvider登录方法调用逻辑分析

DebugView与ProcMon对比指南:哪个更适合你的Windows调试场景? 本文深入对比了DebugView与ProcMon在Windows调试场景中的优劣,帮助开发者根据需求选择合适工具。DebugView擅长捕获程序调试输出,适合快速定位逻辑错误;而ProcMon则提供全面的系统活动监控,适合分析复杂系统交互问题。文章还提供了典型场景的选型指南和高级使用技巧。 阅读详情

前言:在上一篇博客中对CredentialProvider的基本概念与个人理解等做了阐述,其中可知CredentialProvider接口烂,文档不完善、参考资料匮乏、学习门槛高等,但终究还得学习使用,硬着头皮上吧!(查各种资料,找各种样例程序,做各种测试,emo下!)因此,本文对CredentialProvider主要接口类、类方法等的具体用途、调用栈以及登录逻辑进行详细的分析与阐述,一方面强化个人对CredentialProvider的学习,另一方面希望给后来的开发者提供一定的参考!

主要接口类概述:CredentialProvider相关的主要接口类可以简单分为两类,一类是Credential相关类,另一类为Provider相关类。Credential接口类主要包含凭据字段管理、凭据登录验证管理、凭据登录相关事件相应管理等接口,主要包括ICredentialProviderCredential、ICredentialProviderCredential2、ICredentialProviderCredentialWithFieldOptions、IConnectedCredentialProviderCredentialWithFieldOptions几个接口,其中核心接口类为ICredentialProviderCredential,其它凭据类接口均以该核心类为基础类。Provider接口类主要包含验证用户管理、验证用户所对应的登录凭据管理、验证应用场景设定(开机登录、锁屏登录、修改密码、授权验证等)等接口,主要包括ICredentialProvider、ICredentialProviderSetUserArray、ICredentialProviderFilter几个接口(注意:Provider主要管理的是完整的凭据对象,Credential管理的为具体凭据下的UI字段,两者管理的目标不在一个属性层级,Provider管理的是Credential,Credential是Provider的成员对象)。

登录验证调用方法栈获取:CredentialProvider接口相关方法较多,官方文档描述不够完善,各个方法之间的调用顺序仅凭官方文档很难理清楚,此处对样例程序中的各个主要方法中加入OutputDebugStringA(__FUNCTION__)对调用的方法名进行打印,在使用登录插件登录验证过程中使用DebugView进行调试信息捕获,从而得到CredentialProvider在具体登录时的调用方法栈,具体如下图与日志所示(图例非匹配,以日志为主):

00000001	11:12:45.246	[2432] Test_CreateInstance	
00000002	11:12:45.278	[2432] TestProvider::TestProvider	
00000003	11:12:45.559	[2432] TestProvider::SetUsageScenario	
00000004	11:12:45.559	[2432] TestProvider::SetUserArray	
00000005	11:12:45.574	[2432] TestProvider::Advise	
00000006	11:12:45.574	[2432] TestProvider::GetCredentialCount	
00000007	11:12:45.574	[2432] TestProvider::_ReleaseEnumeratedCredentials	
00000008	11:12:45.574	[2432] TestProvider::_CreateEnumeratedCredentials	
00000009	11:12:45.574	[2432] TestProvider::_EnumerateCredentials	
00000010	11:12:45.574	[2432] TestCredential::TestCredential	
00000011	11:12:45.574	[2432] TestCredential::Initialize			
00000015	11:12:45.574	[2432] TestProvider::GetCredentialAt	
00000016	11:12:45.574	[2432] TestProvider::GetFieldDescriptorCount	
00000017	11:12:45.574	[2432] TestProvider::GetFieldDescriptorAt	
00000018	11:12:45.574	[2432] TestProvider::GetFieldDescriptorAt		
00000026	11:12:45.574	[2432] TestCredential::GetBitmapValue	
00000027	11:12:45.574	[2432] TestCredential::GetFieldState	
00000028	11:12:45.574	[2432] TestCredential::GetFieldOptions	
00000029	11:12:45.574	[2432] TestCredential::GetFieldState	
00000030	11:12:45.574	[2432] TestCredential::GetFieldOptions	
00000031	11:12:45.574	[2432] TestCredential::GetSubmitButtonValue	
00000032	11:12:45.574	[2432] TestCredential::GetUserSid		
00000034	11:12:45.621	[2432] TestCredential::Advise	
00000035	11:12:48.730	[2432] TestCredential::SetSelected	
00000036	11:12:49.949	[2432] TestCredential::Connect	
00000037	11:13:06.840	[2432] TestCredential::GetSerialization	
00000038	11:13:06.856	[2432] TestCredential::ReportResult	
00000040	11:13:06.981	[2432] TestCredential::UnAdvise	
00000041	11:13:06.981	[2432] TestProvider::UnAdvise	
00000042	11:13:06.981	[2432] TestProvider::~TestProvider	
00000043	11:13:06.981	[2432] TestCredential::~TestCredential	

主要方法功能详解(按以上调用方法栈顺序为主):

(1)Test_CreateInstance、TestProvider构造函数、~TestProvider析构函数用于创建销毁Provider对象实例,Test_CreateInstance方法为自定义方法而非继承于Provider接口类对象的子方法,具体实现如下:

HRESULT Test_CreateInstance(_In_ REFIID riid, _Outptr_ void **ppv)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr;
    TestProvider *pProvider = new(std::nothrow) TestProvider();
    if (pProvider)
    {
        hr = pProvider->QueryInterface(riid, ppv);
        pProvider->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

(2)SetUsageScenario方法用于设置凭据应用场景,并保存当前应用场景于成员_cpu以用于当前验证场景的其它操作,如下设置的场景为开机登录与锁屏登录场景(补充说明:开机登录场景于锁屏登录场景一次只能触发一个场景,要么是CPUS_LOGON开机登录,要么是CPUS_UNLOCK_WORKSTATION锁屏登录,开机登录只能在开机时触发,锁屏登录场景只能在开机状态下主动或自动锁屏后触发,毕竟该函数传入的cpus参数只有一个值,_cpus也仅有一个值,cpus/_cpus后加的“s”是指该参数可以接收多种场景值,而不是可以同时接收多种场景值):

HRESULT TestProvider::SetUsageScenario(
    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
    DWORD /*dwFlags*/)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr;

    // Decide which scenarios to support here. Returning E_NOTIMPL simply tells the caller
    // that we're not designed for that scenario.
    switch (cpus)
    {
    case CPUS_LOGON:
    case CPUS_UNLOCK_WORKSTATION:
        _cpus = cpus;
        _fRecreateEnumeratedCredentials = true;
        hr = S_OK;
        break;

    case CPUS_CHANGE_PASSWORD:
    case CPUS_CREDUI:
        hr = E_NOTIMPL;
        break;

    default:
        hr = E_INVALIDARG;
        break;
    }

    return hr;
}

(3)SetUserArray设置验证用户,如下设置的为当前系统中所有开机可选择登录的普通用户(补充说明:不包含Administrator、DefaultAccount、Guest、WDAGUtilityAccount四个特殊场景用户,可以使用net user命令查看所有用户):

HRESULT TestProvider::SetUserArray(_In_ ICredentialProviderUserArray *users)
{
    OutputDebugStringA(__FUNCTION__);
    if (_pCredProviderUserArray)
    {
        _pCredProviderUserArray->Release();
    }
    _pCredProviderUserArray = users;
    _pCredProviderUserArray->AddRef();

    return S_OK;
}

(4)Provider接口下的Advise方法与UnAdvise方法,Advise方法传入Provider事件处理对象pcpe与事件触发判定标记upAdviseContext参数用于特定自定义登录事件响应使用,可以用于自定义触发事件代替点击提交按钮使用,最典型的为自动登录功能实现,但个人尚未相关的测试研究,此处不做详细功能实现方法阐述(如遇到后续再来补充),UnAdvise方法个人亦暂未用到不做详细的阐述,两者具体实现如下:

// Called by LogonUI to give you a callback.  Providers often use the callback if they
// some event would cause them to need to change the set of tiles that they enumerated.
HRESULT TestProvider::Advise(
    _In_ ICredentialProviderEvents * pcpe,
    _In_ UINT_PTR upAdviseContext)
{
    OutputDebugStringA(__FUNCTION__);
    _pcpe = pcpe;
    _upAdviseCxt = upAdviseContext;
    return S_OK;
}

// Called by LogonUI when the ICredentialProviderEvents callback is no longer valid.
HRESULT TestProvider::UnAdvise()
{
    OutputDebugStringA(__FUNCTION__);
    return E_NOTIMPL;
}

(5)_ReleaseEnumeratedCredentials、_CreateEnumeratedCredentials、_EnumerateCredentials、TestCredential、Initialize、~TestCredential用于凭据的枚举、实例化、初始化与资源释放,主要实现如下:

void TestProvider::_CreateEnumeratedCredentials()
{
    OutputDebugStringA(__FUNCTION__);
    switch (_cpus)
    {
    case CPUS_LOGON:
    case CPUS_UNLOCK_WORKSTATION:
        {
            _EnumerateCredentials();
            break;
        }
    default:
        break;
    }
}

void TestProvider::_ReleaseEnumeratedCredentials()
{
    OutputDebugStringA(__FUNCTION__);
    if (_pCredential != nullptr)
    {
        _pCredential->Release();
        _pCredential = nullptr;
    }
}

HRESULT TestProvider::_EnumerateCredentials()
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr = E_UNEXPECTED;
    if (_pCredProviderUserArray != nullptr)
    {
        DWORD dwUserCount;
        _pCredProviderUserArray->GetCount(&dwUserCount);
        if (dwUserCount > 0)
        {
            ICredentialProviderUser *pCredUser;
            hr = _pCredProviderUserArray->GetAt(0, &pCredUser);
            if (SUCCEEDED(hr))
            {
                _pCredential = new(std::nothrow) TestCredential();
                if (_pCredential != nullptr)
                {
                    hr = _pCredential->Initialize(_cpus, s_rgCredProvFieldDescriptors, s_rgFieldStatePairs, pCredUser, this->_pcpe, this->_upAdviseCxt);
                    if (FAILED(hr))
                    {
                        _pCredential->Release();
                        _pCredential = nullptr;
                    }
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
                pCredUser->Release();
            }
        }
    }
    return hr;
}

(6)GetCredentialAt方法按索引返回具体验证用户所实例化的凭据对象,具体实现如下:

HRESULT TestProvider::GetCredentialAt(
    DWORD dwIndex,
    _Outptr_result_nullonfailure_ ICredentialProviderCredential **ppcpc)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr = E_INVALIDARG;
    *ppcpc = nullptr;

    if ((dwIndex == 0) && ppcpc)
    {
        hr = _pCredential->QueryInterface(IID_PPV_ARGS(ppcpc));
    }

    return hr;
}

(7)GetFieldDescriptorCount方法设置具体场景所对应的凭据对象中UI字段的数量,开发者需根据具体的验证应用场景设置具体的UI字段数量即可(同一个登录插件可能包含多种验证应用场景,不同的验证应用场景所需对应的凭据对象可能不同,而不同的凭据对应所对应的UI字段数量一般也不同),此处应用场景单一,实现中没有使用switch(_cpu)进行区分设置,具体实现如下:

HRESULT TestProvider::GetFieldDescriptorCount(
    _Out_ DWORD *pdwCount)
{
    OutputDebugStringA(__FUNCTION__);
    *pdwCount = SFI_NUM_FIELDS;
    return S_OK;
}

(8)GetFieldDescriptorAt方法获取凭据UI各个字段的值,调试日志中调用了两次是因为自定义凭据中包含两个UI字段,放法第一个传入的dwIndex参数即为UI字段的索引值,具体实现如下:

HRESULT TestProvider::GetFieldDescriptorAt(
    DWORD dwIndex,
    _Outptr_result_nullonfailure_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr;
    *ppcpfd = nullptr;

    // Verify dwIndex is a valid field.
    if ((dwIndex < SFI_NUM_FIELDS) && ppcpfd)
    {
        hr = FieldDescriptorCoAllocCopy(s_rgCredProvFieldDescriptors[dwIndex], ppcpfd);
    }
    else
    {
        hr = E_INVALIDARG;
    }

    return hr;
}

(9)GetBitmapValue、GetFieldState、GetFieldOptions、GetSubmitButtonValue用于凭据UI初始化显示时对各个UI字段值的获取,类似的还有GetCheckboxValue、GetComboBoxValueCount、GetComboBoxValueAt等。此处所用样例程序中定义的字段如下代码所示:

enum SAMPLE_FIELD_ID
{
    SFI_TILEIMAGE         = 0,
    SFI_SUBMIT_BUTTON     = 1,
    SFI_NUM_FIELDS        = 2,  // Note: if new fields are added, keep NUM_FIELDS last.  This is used as a count of the number of fields
};

前四者被DebugView捕获到是因为自定义样例程序中有其对应的凭据UI字段出现,当微软LogonUI.exe程序检测到要显示的具体的UI字段时才会调用这些方法用于相应字段UI的显示。此处以GetBitmapValue方法的具体实现为例,具体实现如下:

HRESULT TestCredential::GetBitmapValue(DWORD dwFieldID, _Outptr_result_nullonfailure_ HBITMAP *phbmp)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr;
    *phbmp = nullptr;

    if ((SFI_TILEIMAGE == dwFieldID))
    {
        HBITMAP hbmp = LoadBitmap(HINST_THISDLL, MAKEINTRESOURCE(IDB_TILE_IMAGE));
        if (hbmp != nullptr)
        {
            hr = S_OK;
            *phbmp = hbmp;
        }
        else
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
        }
    }
    else
    {
        hr = E_INVALIDARG;
    }

    return hr;
}

而后三者没有被DebugView捕获到时因为自定义样例程序中没有对应的UI字段,故没有被调用,一般没有相应UI字段时对应的“初始化字段值获取方法”只需返回E_INVALIDARG即可。此处以GetCheckboxValue为例,具体代码实现如下:

HRESULT TestCredential::GetCheckboxValue(DWORD dwFieldID, _Out_ BOOL *pbChecked, _Outptr_result_nullonfailure_ PWSTR *ppwszLabel)
{
    OutputDebugStringA(__FUNCTION__);
    return E_INVALIDARG;
}

除了以上用于凭据UI初始化显示时调用的方法外,还有部分用于凭据UI交互相应时调用的方法,典型的如CommandLinkClicked方法,其在用户点击CPFT_COMMAND_LINK按钮时触发,又如SetStringValue方法等,此处内容比较简单,看微软的样例程序及官方的接口文档即可理解,故此处不做太多阐述。

(10)GetUserSid获取当前验证认证用户的SID(安全标识符)值(补充说明:安全标识符(SID)是微软Windows系统中用于唯一标识用户、组或计算机的数值。每个账户在创建时由域控制器分配一个唯一的SID,确保在域环境中实现访问控制),表示用户名在微软中也不能重复,也为唯一表示用户的字段,且在进行认证时使用的为用户名,表示不解该SID的具体用途,但该方法在使用时保证返回值为S_FALSE、设置*ppszSid=nullptr时登录凭据会出现在一个空用户上(登录界面显示一个额外的空用户,登录成功后依然进入默认用户系统,用户图标为CPFT_TILE_IMAGE图像,该场景无意义),此处使用微软官方的样例实现,返回的hr值为S_OK,具体代码如下:

HRESULT TestCredential::GetUserSid(_Outptr_result_nullonfailure_ PWSTR *ppszSid)
{
    OutputDebugStringA(__FUNCTION__);
    
    *ppszSid = nullptr;
    HRESULT hr = E_UNEXPECTED;
    if (_pszUserSid != nullptr)
    {
        hr = SHStrDupW(_pszUserSid, ppszSid);
    }
    // Return S_FALSE with a null SID in ppszSid for the
    // credential to be associated with an empty user tile.

    /**ppszSid = nullptr;
    HRESULT hr = S_FALSE;*/
    return hr;
}

(11)Credential的Advise与UnAdvise方法,Advise传入pcpce事件对象用于相关的事件响应处理,UnAdvise用于事件对象pcpce资源的释放(好像也可以默认不处理),实现如下:

// LogonUI calls this in order to give us a callback in case we need to notify it of anything.
HRESULT TestCredential::Advise(_In_ ICredentialProviderCredentialEvents *pcpce)
{
    OutputDebugStringA(__FUNCTION__);
    if (_pCredProvCredentialEvents != nullptr)
    {
        _pCredProvCredentialEvents->Release();
    }
    return pcpce->QueryInterface(IID_PPV_ARGS(&_pCredProvCredentialEvents));
}

// LogonUI calls this to tell us to release the callback.
HRESULT TestCredential::UnAdvise()
{
    OutputDebugStringA(__FUNCTION__);
    if (_pCredProvCredentialEvents)
    {
        _pCredProvCredentialEvents->Release();
    }
    _pCredProvCredentialEvents = nullptr;
    return S_OK;
}

(12)SetSelected与SetDeselected方法,SetSelected方法在用户点击具体的登录类型图标Logo(CPFT_TILE_IMAGE)时触发,其输出的参数pbAutoLogon可用于设置自动登录(如在输入密码过程中程序自动核验密码正确性,当密码正确时自动登录而无需用户主动按回车键或者鼠标点击登录),正常情况设置*pbAutoLogon=false即可;SetDeselected方法在用户点击其它登录类型图标Logo时触发(当前登录图标logo失去选中焦点时),可用于凭据资源的后处理。此处具体代码示例如下:

HRESULT TestCredential::SetSelected(_Out_ BOOL *pbAutoLogon)
{
    OutputDebugStringA(__FUNCTION__);
    *pbAutoLogon = FALSE;
    //*pbAutoLogon = TRUE;
    return S_OK;
}

// Similarly to SetSelected, LogonUI calls this when your tile was selected
// and now no longer is. The most common thing to do here (which we do below)
// is to clear out the password field.
HRESULT TestCredential::SetDeselected()
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr = S_OK;
    /*if (_rgFieldStrings[SFI_PASSWORD])
    {
        size_t lenPassword = wcslen(_rgFieldStrings[SFI_PASSWORD]);
        SecureZeroMemory(_rgFieldStrings[SFI_PASSWORD], lenPassword * sizeof(*_rgFieldStrings[SFI_PASSWORD]));

        CoTaskMemFree(_rgFieldStrings[SFI_PASSWORD]);
        hr = SHStrDupW(L"", &_rgFieldStrings[SFI_PASSWORD]);

        if (SUCCEEDED(hr) && _pCredProvCredentialEvents)
        {
            _pCredProvCredentialEvents->SetFieldString(nullptr, SFI_PASSWORD, _rgFieldStrings[SFI_PASSWORD]);
        }
    }*/

    return hr;
}

(13)Connect与Disconnect方法,Connect方法用于自定义交互验证使用,Disconnect方法用于自定义交互后的资源释放(暂未用到不做示例展示),Connect示例代码如下:

HRESULT TestCredential::Connect(_In_  IQueryContinueWithStatus* pqcws)
{
    OutputDebugStringA(__FUNCTION__);

    IQueryContinueWithStatus* pStatus = pqcws;
    pStatus->SetStatusMessage(L"请按压指纹进行比对验证...");
    Sleep(1000);

    for (int i = 0; i < 10 && pStatus->QueryContinue() == S_OK; i++) {
        Sleep(1000); // 模拟耗时操作
        std::wstring progressMsg = L"进度: " + std::to_wstring((i + 1) * 10) + L"%";
        pStatus->SetStatusMessage(progressMsg.c_str());
    }
    return S_OK;
}

(14)GetSerialization方法用于凭据验证,一般在点击提交按钮时触发,也可自定义方式触发(特别麻烦,此处不做阐述,微软这接口设计的太死板了,此处吐槽下)由核验参数为用户名与用户所对应的登录密码,示例代码如下:

HRESULT TestCredential::GetSerialization(_Out_ CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpgsr,
                                            _Out_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs,
                                            _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText,
                                            _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon)
{
    OutputDebugStringA(__FUNCTION__);
    HRESULT hr = E_UNEXPECTED;
    if (false == m_authStatus)
    {
        return hr;
    }
    *pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED;
    *ppwszOptionalStatusText = nullptr;
    *pcpsiOptionalStatusIcon = CPSI_NONE;
    ZeroMemory(pcpcs, sizeof(*pcpcs));

    // For local user, the domain and user name can be split from _pszQualifiedUserName (domain\username).
    // CredPackAuthenticationBuffer() cannot be used because it won't work with unlock scenario.
    if (_fIsLocalUser)
    {
        PWSTR pwzProtectedPassword;
        //hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword);
        // 本机用户设置的密码为1
        hr = ProtectIfNecessaryAndCopyPassword(L"1", _cpus, &pwzProtectedPassword);
        if (SUCCEEDED(hr))
        {
                     
            PWSTR pszDomain;
            PWSTR pszUsername;
            hr = SplitDomainAndUsername(_pszQualifiedUserName, &pszDomain, &pszUsername);
            if (SUCCEEDED(hr))
            {
                KERB_INTERACTIVE_UNLOCK_LOGON kiul;
                hr = KerbInteractiveUnlockLogonInit(pszDomain, pszUsername, pwzProtectedPassword, _cpus, &kiul);
                if (SUCCEEDED(hr))
                {
                    // We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios.  It contains a
                    // KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon
                    // as necessary.
                    hr = KerbInteractiveUnlockLogonPack(kiul, &pcpcs->rgbSerialization, &pcpcs->cbSerialization);
                    if (SUCCEEDED(hr))
                    {
                        ULONG ulAuthPackage;
                        hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
                        if (SUCCEEDED(hr))
                        {
                            pcpcs->ulAuthenticationPackage = ulAuthPackage;
                            pcpcs->clsidCredentialProvider = CLSID_Test;
                            // At this point the credential has created the serialized credential used for logon
                            // By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know
                            // that we have all the information we need and it should attempt to submit the
                            // serialized credential.
                            *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
                        }
                    }
                }
                CoTaskMemFree(pszDomain);
                CoTaskMemFree(pszUsername);
            }
            CoTaskMemFree(pwzProtectedPassword);
        }
    }
    
    return hr;
}

(15)ReportResult方法用于GetSerialization方法验证后的结果反馈,示例代码如下:

static const REPORT_RESULT_STATUS_INFO s_rgLogonStatusInfo[] =
{
    { STATUS_LOGON_FAILURE, STATUS_SUCCESS, L"Incorrect password or username.", CPSI_ERROR, },
    { STATUS_ACCOUNT_RESTRICTION, STATUS_ACCOUNT_DISABLED, L"The account is disabled.", CPSI_WARNING },
};

// ReportResult is completely optional.  Its purpose is to allow a credential to customize the string
// and the icon displayed in the case of a logon failure.  For example, we have chosen to
// customize the error shown in the case of bad username/password and in the case of the account
// being disabled.
HRESULT TestCredential::ReportResult(NTSTATUS ntsStatus,
                                        NTSTATUS ntsSubstatus,
                                        _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText,
                                        _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon)
{
    OutputDebugStringA(__FUNCTION__);
    *ppwszOptionalStatusText = nullptr;
    *pcpsiOptionalStatusIcon = CPSI_NONE;

    DWORD dwStatusInfo = (DWORD)-1;

    // Look for a match on status and substatus.
    for (DWORD i = 0; i < ARRAYSIZE(s_rgLogonStatusInfo); i++)
    {
        if (s_rgLogonStatusInfo[i].ntsStatus == ntsStatus && s_rgLogonStatusInfo[i].ntsSubstatus == ntsSubstatus)
        {
            dwStatusInfo = i;
            break;
        }
    }

    if ((DWORD)-1 != dwStatusInfo)
    {
        if (SUCCEEDED(SHStrDupW(s_rgLogonStatusInfo[dwStatusInfo].pwzMessage, ppwszOptionalStatusText)))
        {
            *pcpsiOptionalStatusIcon = s_rgLogonStatusInfo[dwStatusInfo].cpsi;
        }
    }

    // If we failed the logon, try to erase the password field.
    /*if (FAILED(HRESULT_FROM_NT(ntsStatus)))
    {
        if (_pCredProvCredentialEvents)
        {
            _pCredProvCredentialEvents->SetFieldString(nullptr, SFI_PASSWORD, L"");
        }
    }*/

    // Since nullptr is a valid value for *ppwszOptionalStatusText and *pcpsiOptionalStatusIcon
    // this function can't fail.
    //_pCredProvCredentialEvents->SetFieldString(nullptr, SFI_PASSWORD, L"");
    return S_OK;
}

整体性验证逻辑分析:基于以上各个核心方法的具体功能与调用顺序栈在结合微软文档可知——凭据提供程序在登录验证时由微软的LogonUI.exe登录程序调用自定义凭据提供程序dll插件,先构建Provider对象再构建Credential对象,然后根据UI字段内容在登录验证用户界面显示自定义凭据提供者程序UI,凭据UI显示完成后用户通过交互输入验证凭据,点击提交按钮后触发登录验证,凭据验证正确则登录成功,以上即为CredentialProvider程序的基本登录验证逻辑。

构建Windows自定义登录凭证提供程序 在Windows安全架构中,Credential Provider是负责提供用户登录凭证的组件。它是一个COM组件,允许系统在登录过程中查询和验证用户凭据。开发者通过实现特定的接口来创建自定义的Credential Provider。一个基本的Credential Provider需要实现以下几个接口:: 提供核心的登录功能,如获取凭证字段、设置凭证属性等。: 代表一个单一的凭证输入框,如用户名或密码。: 用于回调,当字段状态改变时通知Credential Provider。 阅读详情

相关推荐

华为云Hadoop+OBS数据流水线实战:WordCount端到端打通

Hadoop是分布式批处理的核心框架,OBS(对象存储服务)则是华为云上高可靠、可扩展的数据湖底座。理解HDFS与对象存储的协同原理,掌握MapReduce计算引擎如何通过FileSystem接口对接云存储,是构建云原生大数据平台的基础能力。其技术价值在于解耦存储与计算、支持弹性伸缩,并天然适配高校实验与企业轻量级数据验证场景。本文聚焦华为云环境下的最小可行实践,基于ECS集群部署Hadoop 3.3+OpenJDK 11,通过hadoop-huaweicloud-3.3.4.jar实现OBS无缝集成,完成

weixin_29054399的博客 82

微软 Credential Providers 详解

在 XP 时代,微软提供了 GINA 的接口来让我们对系统登录界面进行定制,在 Win7 以上系统中,微软提供了另外一种接口,就是 Credential Providers, Credential Providers 译为凭据提供者/商。所谓凭据就是提供了一种身份认证的方式,原有的 Windows 身份认证方式就是开机的时候需要你输入密码。而通过微软提供这套接口你可以实现丰富的身份认证方式,比如指...

lionzl的专栏 7819

微软提供的五个CredentialProvider示例代码

微软提供的五个CredentialProvider示例代码,代码规范,适合初学Windows 7/Vista登陆界面替换的朋友学习

自定义windows登录认证(微软 Credential Providers 详解一《调用原理》)

windows登录认证在不同的windows版本中有不同的方式。在xp中使用gina.dll,而在win7及以上版本使用Credential Providers。在win8及以上版本也是使用Credential Providers但是与win7在实现中略有区别。以下文章转载至http://www.mycode.net.cn/platform/windows/1945.html[来源](http://

David 1万+

Win7认证示例CredentialProviders

使用 Windows Vista 的凭据提供程序创造自定义的登录体验 Windows Vista 在平台集成方面为开发人员提供了许多新的机会。新的凭据提供程序模型是变动最大的方面之一,由于它的出现,实现操作系统支持的新用户身份验证方案变得容易了许多。它已取代了 GINA(图形标识与身份验证)模型,而直言不讳地说,后者一向因为开发人员难以理解和实现以及昂贵的 Microsoft 支持费用而广为诟病。 那么 Windows® 登录插件接口的一个变化竟会如此令人兴奋,其原因何在?用户打开计算机时首先看到的是登录屏幕。由于登录体验是由凭据提供程序来控制和管理的,这使得自定义登录体验以及集成最符合组织需要的身份验证方法变得容易了许多。简而言之,凭据提供程序为开发和实现更好、更可靠的安全性提供了一种更容易的方式。

Android 12 HAL开发避坑实录:手把手教你用AIDL在Native层实现一个可开机自启动的Service

本文详细介绍了在Android 12 HAL开发中,如何通过AIDL在Native层实现一个可开机自启动的Service。从环境准备、AIDL接口定义、服务实现到VINTF集成与SELinux策略配置,提供了完整的实战指南,帮助开发者避开常见陷阱,提升开发效率。

weixin_28314575的博客 321

杂记-CredentialProvider(翻译)

转存失败重新上传取消 Windows Vista 凭据提供程序示例概述 目录 使用条款。2个 发行说明。三 样本凭证提供者3 SampleCredentialProvider代码库。4个 项目结构。4个 扩展SampleCredentialProvider 5的常见任务 提示和技巧。7个 自动化部署过程。7个 与CredUI 7一起开发 调试...

天涯过客 4036

javaFx + SpringBoot 启动时报错:No auto configuration classes found in META-INF/spring.factories 解决方法

javaFx+SpringBoot+(de.roskenet:springboot-javafx-support:2.1.6)开发。 直接在idea中运行正常,但打包成jar,之后使用java -jar xxx.jar启动报错,发现日志中有: java.lang.IllegalArgumentException: No auto configuration classes found in META-INF/spring.factories. 以及 at de.felixroske.jfxsuppo

long117long的专栏 4611

CredentialProvider交互式登录实现

文章摘要:Windows CredentialProvider插件开发中,默认ICredentialProviderCredential接口无法实现交互式登录验证。微软提供了IConnectableCredentialProviderCredential接口,通过实现Connect方法可在GetSerialization前执行自定义交互逻辑。开发者可使用IQueryContinueWithStatus参数设置验证提示信息,并支持用户取消操作。文中给出了指纹验证的代码示例,展示了进度提示和取消处理的方法。(1

视觉算法小趴菜的博客 528

命令行删除描述文件_写一个删除lock文件的skill脚本

来源:IC技能搬运工 作者:小目Cadence软件使用过程中偶尔会遇到文件被lock的情况,这是Cadence软件对文件的一种保护措施,大部分情况都是对用户友好的,但是偶尔也会给用户带来困扰,给工作带来的一定的不便。文件是如何被锁的Cadence在用户打开文件进行编辑的时候会在文件的同一级目录新建一个lock文件,用来标识文件的状态。一般情况下如果文件被正常关闭,这个lock文件会在...

weixin_39781452的博客 1352

如何利用东南大学齿轮箱数据集进行故障诊断?从数据采集到模型训练全流程解析

本文详细解析如何利用东南大学齿轮箱数据集进行故障诊断,涵盖从数据采集到模型训练的全流程。通过信号预处理、特征工程构建及多种机器学习模型对比,帮助开发者高效实现工业设备健康管理。特别适合希望将AI技术应用于工业场景的Python开发者。

m2n3b4v5c6的博客 375

CredentialProvider多用户登录实现

本文分析了微软CredentialProvider样例插件在多用户环境下仅显示单个用户登录UI的问题。通过定位代码发现,该插件仅对索引值为0的第一个用户构建了登录凭据。解决方案包括:1)将单一凭据对象改为向量存储;2)修改枚举凭据方法,为每个用户创建凭据实例;3)调整凭据数量获取方法;4)修改凭据获取方法以返回对应索引的凭据;5)更新资源释放逻辑。修改后的代码实现了在所有用户登录界面显示自定义凭据UI的功能,解决了多用户环境下的显示问题。

视觉算法小趴菜的博客 486

手把手教你用TJA1145A实现CAN节点智能唤醒(含数据帧过滤技巧)

本文详细介绍了如何使用TJA1145A收发器实现CAN节点的智能唤醒功能,包括数据帧过滤技巧和超低功耗设计。通过硬件级帧过滤和双条件唤醒机制,TJA1145A在保持微安级休眠电流的同时,能对特定报文做出毫秒级响应,显著提升电池供电设备的续航能力。

weixin_28721917的博客 249

BootShellCredentialProvider核心组件解析:Configurator与CredentialProvider工作原理

BootShellCredentialProvider是一个创新的Windows Credential Provider项目,它允许用户在Windows登录界面直接选择并启动不同的Shell环境,包括Linux桌面环境如KDE、XFCE和Mate。这个工具的核心组件——Configurator配置器和CredentialProvider凭证提供器——共同实现了这一独特功能。本文将深入解析这两个核心

gitblog_00306的博客 457

Chrome红页误报?手把手教你通过Search Console快速解除安全警告

本文详细解析了Chrome红页误报的常见原因及解决方案,重点介绍如何通过Google Search Console快速解除安全警告。内容涵盖误报诊断、技术修复、申诉技巧及长效预防措施,帮助网站管理员有效应对Google Safe Browsing的误判问题,确保网站安全状态快速恢复。

a1b2c的博客 985

简化神经元模型6 -- Hindmarsh-Rose Model

简化神经元模型 6 Hindmarsh-Rose Model

weixin_73557167的博客 1425

CredentialProvider开发

看到网上关于CredentialProvider的开发资料寥寥无几,于是把自己的经验分享出来,不对的望指正. 先百科一下: Windows Vista 在平台集成方面为开发人员提供了许多新的机会。新的凭据提供程序模型是变动最大的方面之一,由于它 的出现,实现操作系统支持的新用户身份验证方案变得容易了许多。它已取代了 GINA(图形标识与身份验证)模型,而直言不讳 地说,后者一向

qq_22038209的博客 4046

【软件测试】- 公有云、私有云、混合云简介以及如何在公有云与私有云上分别搭建测试环境

公有云、私有云以及混合云是目前云计算领域的三种主要模式。它们之间的区别主要在于部署方式、数据访问和管理控制的不同。在阿里云或腾讯云上使用 Kubernetes 和 Docker 搭建测试环境可以提供高度的可伸缩性和灵活性。这样的环境非常适合于需要在多种配置和条件下进行测试的复杂应用。通过云服务提供的工具和服务,可以轻松管理和监控整个测试过程。在云服务上搭建测试环境涉及选择合适的云提供商、配置云资源、安装和设置所需软件等步骤。### 1. 选择云服务提供商。

weixin_46453070的博客 2338

BootShellCredentialProvider开发者指南:从注册表交互到Win32 API调用

BootShellCredentialProvider(简称BSCP)是一个创新的Windows 10 Credential Provider项目,它允许用户在登录Windows时直接进入Linux桌面环境,而不是传统的Windows Explorer。这个项目通过巧妙地结合Windows原生登录界面、注册表操作和Win32 API调用,为开发者提供了一个研究Windows认证机制和shell替换

gitblog_00030的博客 511

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

微软 Credential Providers 详解一《调用原理》

转载自:http://www.mycode.net.cn/platform/windows/1945.html 在 XP 时代,微软提供了 GINA 的接口来让我们对系统登录界面进行定制,在 Win7 以上系统中,微软提供了另外一种接口,就是 Credential Providers, Credential Providers 译为凭据提供者/商。所谓凭据就是提供了一种身份认证的方式,原有的...

ayang1986的专栏 1405

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Keycloak服务开发-认证服务SPI

keycloak的认证服务提供接口

JosephThatwho的博客 4178

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

FaceWinUnlock-Tauri注册表操作与核心组件部署原理:软件如何注入Windows登录界面

**FaceWinUnlock-Tauri** 是一款基于 Tauri 框架开发的 Windows 面容识别解锁软件,它的核心套路是:通过自定义 **Credential Provider(凭据提供程序)DLL** 注入 Windows 登录界面,配合一套精心设计的**注册表操作**,让普通摄像头设备获得类似 Windows Hello 的刷脸解锁体验。这篇文章带你拆解它背后的完整原理:注册表写在

gitblog_01197的博客 6

QuickLook 打不开的受保护 PPTX?三类锁源定位并直接解开

你收到一份 PPT,用 QuickLook 预览,窗口里只剩 "PROTECTED VIEW" 几个字。QuickLook 是把 macOS 的 Quick Look 体验带到 Windows 的工具,OfficeViewer-Native 是它内置的 Office 预览插件。受保护的 PPTX 预览失败,先定位是哪把锁卡住了,再对症下药。 ## 先定位:你的 PPTX 被哪把锁卡住了 看似一

gitblog_00051的博客 346

Umi-OCR 多语言 OCR 快速配置指南:Paddle 引擎语言参数实操

这篇文章带你把 **Umi-OCR** 配 **Paddle** 引擎的 **多语言识别** 一步步设置明白。不用懂 OCR 原理,照着做就能拿到一份能用的配置。因为痛点很真实:中文文档里的英文术语,总被认成乱码。 ## 一|先对号入座:识别失败的三种情况 🔍 **中英混排丢字丢词**。默认单语言配置跑混排文档,引擎会死守一种语言,英文单词要么变乱码,要么直接漏掉。该动的是 **附加语言**

gitblog_00818的博客 222
上一篇: CredentialProvider多用户登录实现
下一篇: CredentialProvider交互式登录实现
ZXF_H
博客等级 码龄9年 134粉丝 106原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值