我使用VistaCredentialProviderSamples中的SampleWrapExistingCredentialProvider开发了一个自定义凭据提供程序。凭据提供程序已实现过滤器,该筛选器可过滤所有其他凭据提供程序,并且在登录时我只能看到我的凭据提供程序。问题是,如果我们使用远程桌面连接连接到它,则用户名/密码不会从Windows RDP客户端传递到凭据提供程序,并且我必须在RDP会话打开时再次输入它(与默认提供程序的行为不同)>
我正在尝试探索代码的哪一部分处理这种情况,即凭据提供者接受来自远程桌面客户端的用户名/密码,并且不再询问。附件是在RDP客户端上提供成功凭据后我的凭据提供程序的屏幕截图。单击凭据提供程序的此图标后,将显示凭据提供程序磁贴,再次询问用户名和密码。非常感谢您提供有关如何从RDP客户端接收凭据的任何帮助。
我为CREDUI返回了S_OK。我的SetUsageScenario如下:
HRESULT CSampleProvider::SetUsageScenario(
CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
DWORD dwFlags
)
{
HRESULT hr;
// Create the password credential provider and query its interface for an
// ICredentialProvider we can use. Once it's up and running, ask it about the
// usage scenario being provided.
IUnknown *pUnknown = NULL;
hr = ::CoCreateInstance(CLSID_PasswordCredentialProvider, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pUnknown));
if (SUCCEEDED(hr))
{
hr = pUnknown->QueryInterface(IID_PPV_ARGS(&(_pWrappedProvider)));
if (SUCCEEDED(hr))
{
hr = _pWrappedProvider->SetUsageScenario(cpus, dwFlags);
switch (cpus)
{
case CPUS_LOGON:
case CPUS_UNLOCK_WORKSTATION:
case CPUS_CREDUI:
{
hr = S_OK;
break;
}
case CPUS_CHANGE_PASSWORD:
default:
hr = E_INVALIDARG;
break;
}
}
}
if (FAILED(hr))
{
if (_pWrappedProvider != NULL)
{
_pWrappedProvider->Release();
_pWrappedProvider = NULL;
}
}
return hr;
}
答案 0 :(得分:0)
您最有可能在寻找ICredentialProvider::SetUsageScenario。 这是我自己的USBLogin的实现。您需要为CPUS_CREDUI返回S_OK:
HRESULT STDMETHODCALLTYPE MyCP::SetUsageScenario(
/* [in] */ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
/* [in] */ DWORD dwFlags)
{
...
switch (cpus)
{
case CPUS_LOGON:
case CPUS_UNLOCK_WORKSTATION:
Scenario = cpus;
hr = S_OK;
break;
case CPUS_CREDUI:
{
int CredUI = 1;// xXML.GetRootElement()->FindElementZ("config",true)->FindVariableZ("credui",true)->GetValueInt();
if (CredUI)
{
Scenario = cpus;
hr = S_OK;
}
else
{
hr = E_NOTIMPL;
}
break;
}
case CPUS_CHANGE_PASSWORD:
hr = E_NOTIMPL;
break;
default:
hr = E_INVALIDARG;
break;
}
return hr;
}
答案 1 :(得分:0)
根据官方文档https:// RDC and Custom Credential Providers
如果用户连接了非Microsoft凭据提供程序,则 您将在终端服务器上提示您再次输入凭据 (两次)。如果未启用NLA,则尽管使用 连接之前客户端上不受支持的凭据提供程序, 用户仍将连接。您将被留在登录 屏幕上,您可以在其中使用受支持的任何凭据提供程序 用于本地身份验证。无法避免两者 使用不受支持的凭据提供程序时进行身份验证。
话虽如此,如果您有自己的凭证提供者并且 尝试与Vista盒进行远程桌面连接(具有此功能 凭据提供商),则您需要登录两次。这是一 预期的行为,这是设计使然,没有合法的方法 避免它。
答案 2 :(得分:0)
用户名/密码未从Windows RDP客户端传递到 凭证提供者,我必须在RDP会话时再次输入 打开(与默认提供程序的行为不同)
Windows无法通过魔术知道客户端的用户名/密码,而该用户名/密码是通过rdp连接的。
在客户端开始时,某些凭据提供程序必须创建CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION
并将其传递给服务器。在其中clsidCredentialProvider
中说出哪个具体提供程序收集此序列化。
该CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION
服务器必须做什么?显然将其传递给某些凭证提供者SetSerialization
方法。但是为什么呢?对所有人 ?没有。再次仅适用于clsid与clsidCredentialProvider
中的CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION
完全匹配的提供者。此提供程序(如果存在且未过滤)必须记住该凭据,然后在被称为GetCredentialCount
时说其具有dafault凭据(而不是CREDENTIAL_PROVIDER_NO_DEFAULT
),并且通常可以使用此凭据进行自动登录。>
从客户端(mstsc)的密码提供者创建序列化。因此将是__uuidof(PasswordCredentialProvider)
中的__uuidof(V1PasswordCredentialProvider)
或clsidCredentialProvider
(如果客户端在win7上运行)。
,但是您在自我过滤器中禁用了此提供程序。结果您自己中断了流程。
过滤器必须实现UpdateRemoteCredential
方法。并在此处复制和更新通过了 pcpcsIn 。这是最重要的一部分,我们必须替换 clsidCredentialProvider 到自己的CLSID。结果将调用我们的SetSerialization
方法。在这里,我们需要还原原始的 CLSID ,然后再将其传递给包装的凭证。
也很重要-在GetCredentialCount
内-首先将其传递给包装的凭证,然后执行*pbAutoLogonWithDefault = FALSE;
-禁用自动登录-如果您需要以下信息(OTP?),则不能执行此操作(自动登录)客户。
UpdateRemoteCredential
内部的方法中,如果声明为const,则不能修改 pcpcsIn 。因此我们需要将更新凭据写入 pcpcsOut 。因为系统无法知道rgbSerialization
所需的大小,所以我们需要自己分配。然后系统将其释放。明显需要使用CoTaskMemAlloc
来分配rgbSerialization
。
所以-所有这些都可以理解,而无需任何文档。但是,如果所有这些都记录在案-也不会太糟。
为UpdateRemoteCredential
编写代码:
HRESULT STDMETHODCALLTYPE CSampleProvider::UpdateRemoteCredential(
/* [annotation][in] */
_In_ const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsIn,
/* [annotation][out] */
_Out_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsOut)
{
if (pcpcsIn->clsidCredentialProvider != __uuidof(PasswordCredentialProvider) &&
pcpcsIn->clsidCredentialProvider != __uuidof(V1PasswordCredentialProvider))
{
// we dont know format of serialization
return E_UNEXPECTED;
}
ULONG cbSerialization = pcpcsIn->cbSerialization;
if (pcpcsOut->rgbSerialization = (PBYTE)CoTaskMemAlloc(cbSerialization + sizeof(GUID)))
{
memcpy(pcpcsOut->rgbSerialization, pcpcsIn->rgbSerialization, cbSerialization);
memcpy(pcpcsOut->rgbSerialization + cbSerialization, &pcpcsIn->clsidCredentialProvider, sizeof(GUID));
pcpcsOut->cbSerialization = cbSerialization + sizeof(GUID);
pcpcsOut->ulAuthenticationPackage = pcpcsIn->ulAuthenticationPackage;
pcpcsOut->clsidCredentialProvider = __uuidof(CSampleProvider);
return S_OK;
}
return E_OUTOFMEMORY;
}
如果我们不知道clsidCredentialProvider
-只需返回E_UNEXPECTED
否则,请分配更多(在sizeof(CLSID)上)内存,最后保存原始clsidCredentialProvider
现在SetSerialization
:
HRESULT STDMETHODCALLTYPE CSampleProvider::SetSerialization(
__in const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs
)
{
if (pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider))
{
// can not query WTSIsRemoteSession, small optimization
_IsRemoteSession = true;
// we got this via ICredentialProviderFilter::UpdateRemoteCredential
ULONG cbSerialization = pcpcs->cbSerialization;
if (cbSerialization >= sizeof(GUID))
{
// restore original clsidCredentialProvider
cbSerialization -= sizeof(GUID);
memcpy(const_cast<GUID*>(&pcpcs->clsidCredentialProvider), pcpcs->rgbSerialization + cbSerialization, sizeof(GUID));
const_cast<CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION*>(pcpcs)->cbSerialization = cbSerialization;
}
}
return _pWrappedProvider->SetSerialization(pcpcs);
}
还原原始clsidCredentialProvider
并修复cbSerialization
。也因为在我的情况下pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider)
只能在UpdateRemoteCredential
内设置(对于RDP,我不能在客户端使用CPUS_CREDUI
,而只能对“以管理员身份运行”)–我只知道这是远程连接并保存此信息(_IsRemoteSession = true;
)以便不调用WTSIsRemoteSession
最后GetCredentialCount
:
HRESULT STDMETHODCALLTYPE CSampleProvider::GetCredentialCount(
__out DWORD* pdwCount,
__out_range(<,*pdwCount) DWORD* pdwDefault,
__out BOOL* pbAutoLogonWithDefault
)
{
HRESULT hr = _pWrappedProvider->GetCredentialCount(pdwCount, pdwDefault, pbAutoLogonWithDefault);
*pbAutoLogonWithDefault = FALSE;//!!!
return hr;
}
请注意非常重要的*pbAutoLogonWithDefault = FALSE;//!!!
行