1

对于 Winforms 桌面应用程序,我将使用 PKCE 的授权代码流。作为身份提供者,我使用IdentityServer和客户端库 OicdClient。下一步我必须决定使用哪个浏览器进行用户登录:

对于 SystemBrowser 来说,流程的简单/清晰的实现。对于 Extended WebBrowser 来说,有些用户可能没有 SystemBrowser。但是 WebBrowser 是一个旧的 IE 版本?是否允许用于安全身份验证?

尽管如此,我还是尝试了“Extended WebBrowser”示例,并偶然将它集成到我的原型环境中,并使用自己的 IS4 服务器。因此,我需要对代码流和重定向有所了解。我已经用纯 .Net 类实现了这个授权代码流,但是使用 OicdClient 让我有点困惑(一开始就像一个黑盒子)。

我的问题是重定向如何与这个库一起工作,谁负责重定向,谁负责接收带有代码的重定向(以交换访问令牌)?

代码流包含以下步骤(没有详细信息,如 clientID、PKCE ...):

  1. 向 IS4 发送代码请求
  2. 带有登录页面的 IS4 响应(显示在浏览器中)
  3. 成功登录后 IS4 发送到重定向 URL 的代码
  4. HttpListener 使用代码接收此重定向,并且
  5. 使用代码向 IS4 发送请求以接收访问令牌

使用 OidcClient 并使用自动模式:

var options = new OidcClientOptions
{
    Authority = "https://demo.identityserver.io",
    ClientId = "native",
    RedirectUri = redirectUri,
    Scope = "openid profile api",
    Browser = new SystemBrowser()
};

var client = new OidcClient(options);
var result = await client.LoginAsync();

这对我来说太神奇了。只有调用 LoginAsync() 才能使其工作......

重要的一点似乎是带有 IBrowser 接口的选项的 Browser 属性及其对该方法的实现:

    public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken)
    {
            using (var listener = new LoopbackHttpListener(Port, _path))
            {
                OpenBrowser(options.StartUrl);
                try
                {
                    var result = await listener.WaitForCallbackAsync();
                    if (String.IsNullOrWhiteSpace(result))
                    {
                        return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." };
                    }
                    return new BrowserResult { Response = result, ResultType = BrowserResultType.Success };
                }
                catch (TaskCanceledException ex)
                { ....}
            }
        }

如果我尝试映射到流程步骤:

  1. 登录页面:OpenBrowser(options.StartUrl);
  2. 重定向将由 IS4 完成?示例中的 SystemBrowser 不执行此操作。
  3. 接收代码:await listener.WaitForCallbackAsync();

1 和 5 可能是由 OicdClient 完成的。这个例子很清楚,需要确认重定向是由IS4完成的。

另一个示例扩展 WebBrowser中的实现

public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var form = _formFactory.Invoke())
            using (var browser = new ExtendedWebBrowser()
            {
                Dock = DockStyle.Fill
            })
            {
                var signal = new SemaphoreSlim(0, 1);

                var result = new BrowserResult
                {
                    ResultType = BrowserResultType.UserCancel
                };

                form.FormClosed += (o, e) =>
                {
                    signal.Release();
                };

                browser.NavigateError += (o, e) =>
                {
                    e.Cancel = true;

                    if (e.Url.StartsWith(options.EndUrl))
                    {
                        result.ResultType = BrowserResultType.Success;
                        result.Response = e.Url;
                    }
                    else
                    {
                        result.ResultType = BrowserResultType.HttpError;
                        result.Error = e.StatusCode.ToString();
                    }

                    signal.Release();
                };

                browser.BeforeNavigate2 += (o, e) =>
                {
                    var b = e.Url.StartsWith(options.EndUrl);
                    if (b)
                    {
                        e.Cancel = true;
                        result.ResultType = BrowserResultType.Success;
                        
                        result.Response = e.Url;
                        
                        signal.Release();
                    }
                };

                form.Controls.Add(browser);
                browser.Show();

                System.Threading.Timer timer = null;

                form.Show();
                browser.Navigate(options.StartUrl);

                await signal.WaitAsync();
                if (timer != null) timer.Change(Timeout.Infinite, Timeout.Infinite);

                form.Hide();
                browser.Hide();

                return result;
            }
        }
  1. 完成:browser.Navigate(options.StartUrl);
  2. 由 IS4 重定向
  3. 接收事件句柄中的代码:NavigateError ???

这里有什么问题吗?在 IS4 上调用 AccountController.Login 来调用 /connect/authorize/callback? 使用redirect_uri。但这并没有出现在 BeforeNavigate2 中。相反,NavigateError 事件出现在结果设置为:

result.ResultType = BrowserResultType.Success;
result.Response = e.Url; 
4

1 回答 1

0

当前的最佳实践是使用用户的默认 Web 浏览器,而不是嵌入浏览器组件。至于如何实现它 - 由于您无法使用这种方法拦截浏览器导航事件,因此您需要实现一个 HTTP 侦听器,该侦听器可以接受来自您的identityserver4实现的 POST 请求。

阅读以下内容:https ://auth0.com/blog/oauth-2-best-practices-for-native-apps/

而这个 RFC:https ://www.rfc-editor.org/rfc/rfc8252

于 2019-12-13T14:00:09.633 回答