对于 Winforms 桌面应用程序,我将使用 PKCE 的授权代码流。作为身份提供者,我使用IdentityServer和客户端库 OicdClient。下一步我必须决定使用哪个浏览器进行用户登录:
对于 SystemBrowser 来说,流程的简单/清晰的实现。对于 Extended WebBrowser 来说,有些用户可能没有 SystemBrowser。但是 WebBrowser 是一个旧的 IE 版本?是否允许用于安全身份验证?
尽管如此,我还是尝试了“Extended WebBrowser”示例,并偶然将它集成到我的原型环境中,并使用自己的 IS4 服务器。因此,我需要对代码流和重定向有所了解。我已经用纯 .Net 类实现了这个授权代码流,但是使用 OicdClient 让我有点困惑(一开始就像一个黑盒子)。
我的问题是重定向如何与这个库一起工作,谁负责重定向,谁负责接收带有代码的重定向(以交换访问令牌)?
代码流包含以下步骤(没有详细信息,如 clientID、PKCE ...):
- 向 IS4 发送代码请求
- 带有登录页面的 IS4 响应(显示在浏览器中)
- 成功登录后 IS4 发送到重定向 URL 的代码
- HttpListener 使用代码接收此重定向,并且
- 使用代码向 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)
{ ....}
}
}
如果我尝试映射到流程步骤:
- 登录页面:OpenBrowser(options.StartUrl);
- 重定向将由 IS4 完成?示例中的 SystemBrowser 不执行此操作。
- 接收代码: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;
}
}
- 完成:browser.Navigate(options.StartUrl);
- 由 IS4 重定向
- 接收事件句柄中的代码:NavigateError ???
这里有什么问题吗?在 IS4 上调用 AccountController.Login 来调用 /connect/authorize/callback? 使用redirect_uri。但这并没有出现在 BeforeNavigate2 中。相反,NavigateError 事件出现在结果设置为:
result.ResultType = BrowserResultType.Success;
result.Response = e.Url;