TL; DR: Я столкнулся (как минимум) с 2 проблемами: недействительными сертификатами SSL и проблемами с токеном Kerberos.В моей тестовой настройке установлены локальные компьютеры с веб-сервером, на который я звоню.Эти локальные компьютеры являются в основном виртуальными машинами ОС Windows с самозаверяющими сертификатами.Некоторые из них являются серверами Windows.Последний работал, а морозил нет.С IE оба работали.
Переход на рассматриваемый сайт с использованием https://...
приводит к тому, что CEFsharp обнаруживает самозаверяющий сертификат (который не является частью доверенной цепочки сертификатов) - поэтому он будет вызыватьбраузеры RequestHandler (если установлено) и вызов его
public override bool OnCertificateError (IWebBrowser browserControl, IBrowser browser,
CefErrorCode errorCode, string requestUrl,
ISslInfo sslInfo, IRequestCallback callback)
{
Log.Logger.Warn (sslInfo.CertStatus.ToString ());
Log.Logger.Warn (sslInfo.X509Certificate.Issuer);
if (CertIsTrustedEvenIfInvalid (sslInfo.X509Certificate))
{
Log.Logger.Warn ("Trusting: " + sslInfo.X509Certificate.Issuer);
if (!callback.IsDisposed)
using (callback)
{
callback?.Continue (true);
}
return true;
}
else
{
return base.OnCertificateError (browserControl, browser, errorCode, requestUrl,
sslInfo, callback);
}
}
Для целей тестирования я жестко закодировал некоторые тесты в CertIsTrustedEvenIfInvalid (sslInfo.X509Certificate)
, которые возвращали бы true
для моей тестовой среды - это можетбыть заменен простым return false
, всплывающим пользовательским интерфейсом, представляющим сертификат и спрашивающим пользователя, хочет ли он продолжить, или он может принять во внимание определенные предоставленные пользователем файлы сертификата - не знаю пока:
bool CertIsTrustedEvenIfInvalid (X509Certificate certificate)
{
var debug = new Dictionary<string, HashSet<string>> (StringComparer.OrdinalIgnoreCase)
{
["cn"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "some", "data" },
["ou"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "other", "stuff" },
["o"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "..." },
["l"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Atlantis" },
["s"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Outer Space" },
["c"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "whatsnot" },
};
var x509issuer = certificate.Issuer
.Split (",".ToCharArray ())
.Select (part => part.Trim().Split("=".ToCharArray(), 2).Select (p => p.Trim()))
.ToDictionary (t => t.First (), t => t.Last ());
return x509issuer.All (kvp => debug.ContainsKey (kvp.Key) &&
debug[kvp.Key].Contains (kvp.Value));
}
Только если SSL-Step работает, будет предпринята попытка SSO.
После решения проблемы с SSL я столкнулся с различным поведением Chrome по сравнению с IE / Firefox и т. Д., Как описано здесь @ Выбор схемы аутентификации - суть ее:
- если сервер сообщает о нескольких схемах аутентификации, IE / Firefox использует первую известную ему - доставленную сервером (предпочтение по порядку)
- Chrome использует ту, которая ему известнасчитается наивысшим приоритетом (в порядке:
Negotiate
-> NTLM
-> Digest
-> Basic
), игнорируя упорядочение серверов альтернативных схем.
Мои серверы сообщили NTLM,Negotiante
(этот порядок) - с IE это просто работало.
С Chrome это привело к обмену токенов Kerberos - который работал только тогда, когда веб-сервер был размещен в ОС Windows Server, - а не для ОС Windows Client.Возможно, какая-то неудачная конфигурация для компьютеров с клиентской ОС в AD использовалась.Не уверен, хотя - но в отношении серверной ОС это работает.
Дополнительно я реализовал
public override bool GetAuthCredentials (IWebBrowser browserControl, IBrowser browser,
IFrame frame, bool isProxy, string host,
int port, string realm, string scheme,
IAuthCallback callback)
{
// pseudo code - asks for user & pw
(string UserName, string Password) = UIHelper.UIOperation (() =>
{
// UI to ask for user && password:
// return (user,pw) if input ok else return (null,null)
});
if (UserName.IsSet () && Password.IsSet ())
{
if (!callback.IsDisposed)
{
using (callback)
{
callback?.Continue (UserName, Password);
}
return true;
}
}
return base.GetAuthCredentials (browserControl, browser, frame, isProxy,
host, port, realm, scheme, callback);
}
, чтобы обеспечить восстановление после сбоя, если SSO не сработал.После предоставления учетных данных AD в этом диалоговом окне также возможен вход в систему).
Для хорошей меры я также внес в белый список хосты в контексте CEF-Browser при создании нового посредника, например:
CefSharp.RequestContext CreateNewRequestContext (string subDirName, string host,
WebConnectionType conType)
{
var contextSettings = new RequestContextSettings
{
PersistSessionCookies = false,
PersistUserPreferences = false,
CachePath = Path.Combine (Cef.GetGlobalRequestContext ().CachePath, subDirName),
};
var context = new CefSharp.RequestContext (contextSettings);
if (conType == WebConnectionType.Negotiate) # just an enum for UserPW + Negotiate
Cef.UIThreadTaskFactory.StartNew (() =>
{
// see https://cs.chromium.org/chromium/src/chrome/common/pref_names.cc for names
var settings = new Dictionary<string, string>
{
["auth.server_whitelist"] = $"*{host}*",
["auth.negotiate_delegate_whitelist"] = $"*{host}*",
// only set-able via policies/registry :/
// ["auth.schemes"] = "ntlm" // "basic", "digest", "ntlm", "negotiate"
};
// set the settings - we *trust* the host with this and allow negotiation
foreach (var s in settings)
if (!context.SetPreference (s.Key, s.Value, out var error))
Log.Logger.Debug?.Log ($"Error setting '{s.Key}': {error}");
});
return context;
}