我已成功使用IdentityModel.OidcClient v2中的WinForms示例来调用使用IdentityServer4保护的API .
IS配置了两个外部提供商,Google和ADFS;实现基于IS4快速入门 .
身份验证工作正常,WinForms应用程序接收有效的刷新令牌,并能够调用安全的API,但我对外部登录回调行为感到困惑 .
成功登录后,嵌入式浏览器关闭,默认浏览器打开(我的笔记本电脑中的Chrome),并到达ExternalLoginCallback .
然后WinForms获取刷新令牌,但随后chrome选项卡保持打开状态并重定向到IS登录页面 .
如何阻止显示/关闭Chrome浏览器窗口?我是否必须调整ExternalLogin操作?
Update
添加客户端代码和lib /服务器信息:
具有IdentityModel v 3.0.0的WinForm客户端IdentityModel.OidcClient 2.4.0具有IdentityServer4版本2.1.1的asp.net mvc服务器IdentityServer4.EntityFramework 2.1.1
遵循WinForm客户端代码:
public partial class SampleForm : Form
{
private OidcClient _oidcClient;
private HttpClient _apiClient;
public SampleForm()
{
InitializeComponent();
var options = new OidcClientOptions
{
Authority = "http://localhost:5000",
ClientId = "native.hybrid",
ClientSecret = "secret",
Scope = "openid email offline_access myscope myapi1 myapi2",
RedirectUri = "http://localhost/winforms.client",
ResponseMode = OidcClientOptions.AuthorizeResponseMode.FormPost,
Flow = OidcClientOptions.AuthenticationFlow.Hybrid,
Browser = new WinFormsEmbeddedBrowser()
};
_oidcClient = new OidcClient(options);
}
private async void LoginButton_Click(object sender, EventArgs e)
{
AccessTokenDisplay.Clear();
OtherDataDisplay.Clear();
var result = await _oidcClient.LoginAsync(new LoginRequest());
if (result.IsError)
{
MessageBox.Show(this, result.Error, "Login", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
AccessTokenDisplay.Text = result.AccessToken;
var sb = new StringBuilder(128);
foreach (var claim in result.User.Claims)
{
sb.AppendLine($"{claim.Type}: {claim.Value}");
}
if (!string.IsNullOrWhiteSpace(result.RefreshToken))
{
sb.AppendLine($"refresh token: {result.RefreshToken}");
}
OtherDataDisplay.Text = sb.ToString();
_apiClient = new HttpClient(result.RefreshTokenHandler);
_apiClient.BaseAddress = new Uri("http://localhost:5003/");
}
}
private async void LogoutButton_Click(object sender, EventArgs e)
{
//await _oidcClient.LogoutAsync(trySilent: Silent.Checked);
//AccessTokenDisplay.Clear();
//OtherDataDisplay.Clear();
}
private async void CallApiButton_Click(object sender, EventArgs e)
{
if (_apiClient == null)
{
return;
}
var result = await _apiClient.GetAsync("identity");
if (result.IsSuccessStatusCode)
{
OtherDataDisplay.Text = JArray.Parse(await result.Content.ReadAsStringAsync()).ToString();
}
else
{
OtherDataDisplay.Text = result.ReasonPhrase;
}
}
}
Update 2
ExternalLoginCallback代码:
public async Task<IActionResult> ExternalLoginCallback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
_logger.LogError(result.Failure, "External athentication error.");
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var externalUser = result.Principal;
var claims = externalUser.Claims.ToList();
....LOOKING FOR THE USER (OMITTED FOR BREVITY)....
var additionalClaims = new List<Claim>();
// if the external system sent a session id claim, copy it over
// so we can use it for single sign-out
var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// if the external provider issued an id_token, we'll keep it for signout
AuthenticationProperties props = null;
var id_token = result.Properties.GetTokenValue("id_token");
if (id_token != null)
{
props = new AuthenticationProperties();
props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
}
// issue authentication cookie for user
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, userId, user.Id.ToString(), user.Username));
await HttpContext.SignInAsync(user.Id.ToString(), user.Username, provider, props, additionalClaims.ToArray());
_logger.LogInformation("User {user} logged in with external provider.", userId);
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
// validate return URL and redirect back to authorization endpoint or a local page
var returnUrl = result.Properties.Items["returnUrl"];
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("~/");
}
IdentityServer上的客户端配置,已序列化:
{
"Enabled": true,
"ClientId": "native.hybrid",
"ProtocolType": "oidc",
"RequireClientSecret": true,
"ClientName": "Application",
"LogoUri": null,
"RequireConsent": false,
"AllowRememberConsent": true,
"AllowedGrantTypes": [
"hybrid"
],
"RequirePkce": false,
"AllowPlainTextPkce": false,
"AllowAccessTokensViaBrowser": true,
"RedirectUris": [
"http://localhost/winforms.client"
],
"FrontChannelLogoutUri": null,
"FrontChannelLogoutSessionRequired": true,
"BackChannelLogoutUri": null,
"BackChannelLogoutSessionRequired": true,
"AllowOfflineAccess": true,
"AllowedScopes": [
"openid",
"email",
"profile",
"myscope",
"offline_access",
"myapi1",
"myapi2"
],
"AlwaysIncludeUserClaimsInIdToken": false,
"IdentityTokenLifetime": 300,
"AccessTokenLifetime": 3600,
"AuthorizationCodeLifetime": 300,
"AbsoluteRefreshTokenLifetime": 2592000,
"SlidingRefreshTokenLifetime": 1296000,
"ConsentLifetime": null,
"RefreshTokenUsage": 1,
"UpdateAccessTokenClaimsOnRefresh": false,
"RefreshTokenExpiration": 1,
"AccessTokenType": 0,
"EnableLocalLogin": true,
"IdentityProviderRestrictions": [
"Google",
"WsFederation"
],
"IncludeJwtId": false,
"Claims": [],
"AlwaysSendClientClaims": false,
"ClientClaimsPrefix": "client_",
"PairWiseSubjectSalt": null,
"Properties": {}
}
2 回答
我可以回答很长时间,但最后你使用的快速启动代码是这个问题的根本原因 . 确切地说,这是引起问题的代码:
它应该成为这样:
这也意味着您需要一个扩展类方法:
缺少的viewmodel:
这个缺少的javascript文件,其内容位于wwwroot / js / signin-redirect.js中
最后一个位于Views / Shared中的新剃刀页面Redirect.cshtml
这应该可以解决问题,或者您可以更新快速入门代码 . 但这不是你自己的代码中的问题 .
我认为
ResponseMode
是困扰你的事情 . 为什么不从OIDC客户端设置中删除它 . 流程也可以用于现在(只需确保在IDS端正确配置) . 此外 - 监视Identity Server的日志,查找任何错误 .