Подключение к удаленному рабочему столу с поставщиком пользовательских учетных данных - PullRequest
0 голосов
/ 26 мая 2019

Я разработал пользовательский поставщик учетных данных, используя SampleWrapExistingCredentialProvider из VistaCredentialProviderSamples. В поставщике учетных данных реализован фильтр, который фильтрует всех других поставщиков учетных данных, и я вижу только моего поставщика учетных данных во время входа в систему. Проблема заключается в том, что если мы подключаемся к нему с помощью подключения к удаленному рабочему столу, имя пользователя / пароль не передаются из клиента Windows RDP провайдеру учетных данных, и мне приходится вводить его снова при открытии сеанса RDP (в отличие от поведения с провайдером по умолчанию)

Я пытаюсь выяснить, какая часть кода обрабатывает этот сценарий, когда поставщик учетных данных принимает имя пользователя / пароль от клиента удаленного рабочего стола и больше не запрашивает. Прикрепленный снимок экрана моего провайдера учетных данных после предоставления успешных учетных данных на клиенте RDP. После того, как я щелкну по этому значку своего провайдера учетных данных, мне будет показан тайл провайдера учетных данных, который снова запрашивает имя пользователя и пароль. Любая помощь будет высоко цениться о том, как получить учетные данные от клиента RDP. This is the logon screen that appears after I enter username password on remote desktop connection

This appears after I select my CP. Username and password not filled up

Я вернул S_OK для CREDUI. Мой 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;
}

Ответы [ 2 ]

0 голосов
/ 14 июля 2019

имя пользователя / пароль не передаются от клиента Windows RDP к Поставщик учетных данных и я должен ввести его снова, когда сеанс RDP открывает (в отличие от поведения с поставщиком по умолчанию)

окна не могут каким-то волшебством узнать имя пользователя / пароль клиента, которые подключаются по rdp.

в начале на стороне клиента какой-либо поставщик учетных данных должен создать CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION и передать его на сервер. внутри него clsidCredentialProvider говорят, какой конкретный поставщик собирает эту сериализацию.

что сервер должен делать с этим CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION? очевидно, передайте его какому-либо поставщику учетных данных SetSerialization метод. но для чего? для всех ? нет. опять же, только для провайдера, который точно соответствует clsid clsidCredentialProvider из CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION. этот провайдер (если он существует и не отфильтрован) должен запомнить эти учетные данные, а затем при вызове GetCredentialCount - сказать, что у него есть учетные данные по умолчанию (не CREDENTIAL_PROVIDER_NO_DEFAULT), и он обычно готов к попытке автоматического входа с этим.

со стороны клиента (mstsc) поставщик паролей создает сериализацию. так будет __uuidof(PasswordCredentialProvider) или __uuidof(V1PasswordCredentialProvider) (если клиент работает на win7) в clsidCredentialProvider.

но вы отключаете этот провайдер в самофильтре. в результате вы сами нарушаете процесс.

Фильтр

должен реализовывать метод UpdateRemoteCredential. и вот скопируйте и обновите передали pcpcsIn . самая важная часть этого - мы должны заменить clsidCredentialProvider на собственный CLSID. в результате будет вызван наш метод SetSerialization. здесь нам нужно восстановить оригинал CLSID , прежде чем передать его в упакованные учетные данные.

также важное место - внутри GetCredentialCount - сначала передайте его в завернутые учетные данные, а затем выполните *pbAutoLogonWithDefault = FALSE; - отключите автологон - вы не можете сделать это (автологон), если вам требуется дополнительный (OTP?) информация от клиента.

внутри UpdateRemoteCredential метод, который мы не можем изменить pcpcsIn - если он объявлен как const. поэтому нам нужно написать наше обновление для 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 в моем случае (я не делаю CPUS_CREDUI на стороне клиента для RDP, только для "запуска от имени администратора") - я просто знаю, что это удаленное соединение, и сохраните эту информацию (_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;//!!! строка

0 голосов
/ 28 мая 2019

Согласно официальной документации https: Поставщики RDC и пользовательских учетных данных

Если пользователь подключен к поставщику учетных данных не от Microsoft, на терминальном сервере вам будет предложено:введите учетные данные еще раз (дважды).Если NLA не включен, то, несмотря на вход с использованием неподдерживаемого поставщика учетных данных на клиенте до подключения, пользователь все равно будет подключен.Вы останетесь на экране входа в систему, где вы можете использовать любой провайдер учетных данных, который поддерживается для локальной аутентификации.Невозможно избежать двух аутентификаций при использовании неподдерживаемых провайдеров учетных данных.

Сказав это, если у вас есть свои собственные провайдеры учетных данных, и вы пытаетесь подключиться к удаленному рабочему столу на компьютере Vista (с этим провайдером учетных данных)тогда вам нужно будет войти в систему дважды.Это ожидаемое поведение, и оно разработано, и нет законного способа избежать этого.

...