如何修复 SSO 与 cefSharp 离线浏览器连接到由 self-signed PC 托管的 Web 服务器?

How to fix SSO connet with cefSharp offline browser to webserver hosted by self-signed PC?


TL;DR: 我正在寻找救命稻草,任何人都在使用 CefSharp 的情况下获得了 SSO,并且可以指出我做错了什么?我尝试通过 CefSharp 连接到 SSL-SSO 页面,但它不起作用 - 在 Chrome-Browser 中也不起作用。对于 IE,它 可以正常工作 。我将其添加到受信任的站点 (Proxy/Security),我尝试在注册表中将 whitelist-policy URL 用于 chrome,并尝试了不同的 CefSharp 设置 - 没有任何帮助。


我正在尝试(无济于事)通过 CefSharp-Offline-浏览连接到启用 SSO 的页面。

用普通IE浏览正常:

使用 Chrome 69.0.3497.100 浏览失败:

我想这可能是因为网络服务器是在 co-workers PC 上设置的并且使用了 self-signed 证书。


F12-在IE/Chrome中调试:

(302 之一)请求 headers 的主要区别是 NEGOTIATENTLM

// IE:
Authorization: NTLM TlRMT***==

// Chrome:
Authorization: Negotiate TlRMT***==
Upgrade-Insecure-Requests: 1
DNT: 1

到目前为止,我无法通过 CefSharp 进行连接,我只是登陆了它 RequestHandler.GetAuthCredentials() - 我不想通过它传递任何凭据。


我试图让它在内部工作的内容 Windows / Chrome:

总而言之什么也没做:我仍然没有使用 Chrome 获得任何 SSO:


我试图让它在 CefSharp 中工作:

var settings = new CefSettings { IgnoreCertificateErrors = true, ... };
settings.CefCommandLineArgs.Add ("auth-server-whitelist", "*host-url*");
settings.CefCommandLineArgs.Add ("auth-delegate-whitelist", "*host-url*");
var browser = new CefSharp.OffScreen.ChromiumWebBrowser (
    "", requestContext: CreateNewRequestContext (webContext.Connection.Name));

<pre>CefSharp.RequestContext CreateNewRequestContext (string connName) { var subDirName = Helper.Files.FileHelper.MakeValidFileSystemName (connName); var contextSettings = new RequestContextSettings { PersistSessionCookies = false, PersistUserPreferences = false, CachePath = Path.Combine (Cef.GetGlobalRequestContext ().CachePath, subDirName), IgnoreCertificateErrors = true, }; // ... return new CefSharp.RequestContext (contextSettings); }

我知道这些更改的一部分是多余的(f.e。设置白名单的 3 种方法,其中至少 2 种适用于 CefSharp,不确定影响它的注册表)以及万一IgnoreCertificateErrors 很危险,不能留在里面。我只是希望它能以某种方式工作,然后 trim 返回该怎么做才能使其在生产中工作。


研究:

和其他人 .. 仍然 none 更聪明。


问题:我正在寻找救命稻草,有人在使用 CefSharp 的情况下获得了 SSO 并且可以指出我做错了什么吗?


TL;DR: 我遇到(至少)2 个问题:无效的 SSL 证书和 Kerberos 令牌问题。我的测试设置在本地计算机上设置了我调用的网络服务器。这些本地计算机大多是 windows 具有自签名证书的客户端 OS 虚拟机。有些是 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) 中,这将 return true 用于我的测试环境 - 这可能会被简单的 return false、UI-弹出窗口显示证书并询问用户是否要继续,或者它可能会考虑某些用户提供的证书文件 - 还不知道:

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 服务器 OS 上时有效 - 不适用于 Windows客户 OS。可能是 AD 中使用的 Client-OS 计算机的某种失败配置。虽然不确定 - 但针对服务器 OS 它有效。

另外我实现了

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 浏览器上下文的白名单,如下所示:

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;
}