使用 Identitity4 和 OidcClient 的授权代码流

Authorization code flow with Identitity4 and OidcClient

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

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

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

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

代码流有以下步骤(没有 clientID、PKCE 等细节......):

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

使用 OidcClient 并使用 Automatic Mode:

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() 才能使其工作...

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

    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完成的。

另一个例子中的实现Extended 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; 

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

读一读:https://auth0.com/blog/oauth-2-best-practices-for-native-apps/

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