如何处理使用 IdentityServer3 作为身份验证服务器的帖子
How to handle posts with IdentityServer3 as authentication server
TL;DR
如何在 ASP.NET MVC 项目(表单,jQuery,axios)中使用 IdentityServer3 作为身份验证服务器来 POST 数据。另外,要使用什么流程才能完成这项工作?
我正在经历的事情
我有一个可用的 IdentityServer3 实例。我还有一个 ASP.NET MVC 项目。使用混合流,因为我必须将用户的令牌传递给其他服务。身份验证本身有效 - 当页面仅使用 GET 时。即使经过身份验证的用户的令牌已过期,后台也会将请求重定向到 auth。服务器,用户可以继续它的工作,而无需要求用户再次登录。 (据我所知,混合流程可以使用刷新令牌,所以我认为这就是它可以重新验证用户身份的方式。即使 HttpContext.Current.User.Identity.IsAuthenticated=false
)
出于测试目的,我在身份验证中将 AccessTokenLifetime
、AuthorizationCodeLifetime
和 IdentityTokenLifetime
值设置为 5 秒。服务器。据我所知,刷新令牌的过期时间以天为单位,我没有更改默认值。
但是当我尝试使用 POST 时,事情变得 "ugly"。
- 使用表单 POST,使用过期的令牌,请求被重定向到 IdentityServer3。它确实很神奇(用户获得身份验证)并重定向到我的页面 - 作为 GET 请求...我在 URL 中看到
response_mode=form_post
,但发布的有效负载消失了。
- 使用 axios POST,请求被重定向到 IdentityServer3,但在飞行前的 OPTIONS 请求中失败。
- 使用默认的 jQuery POST,得到同样的错误。 (尽管如此,默认的 jQuery POST 使用
application/x-www-form-urlencoded
来解决飞行前问题。)
startup.cs
const string authType = "Cookies";
// resetting Microsoft's default mapper
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
// ensure, that the MVC anti forgery key engine will use our "custom" user id
AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = authType
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
RedirectUri = adminUri,
PostLogoutRedirectUri = adminUri,
Authority = idServerIdentityEndpoint,
SignInAsAuthenticationType = authType,
ResponseType = "code id_token",
Scope = "openid profile roles email offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
#region Handle automatic redirect (on logout)
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType ==
OpenIdConnectRequestType.LogoutRequest)
{
var token = n.OwinContext.Authentication.User.FindFirst(idTokenName);
if (token != null)
{
var idTokenHint =
token.Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
},
#endregion
AuthorizationCodeReceived = async n =>
{
System.Diagnostics.Debug.Print("AuthorizationCodeReceived " + n.ProtocolMessage.ToString());
// fetch the identity from authentication response
var identity = n.AuthenticationTicket.Identity;
// exchange the "code" token for access_token, id_token, refresh_token, using the client secret
var requestResponse = await OidcClient.CallTokenEndpointAsync(
new Uri(idServerTokenEndpoint),
new Uri(adminUri),
n.Code,
clientId,
clientSecret
);
// fetch tokens from the exchange response
identity.AddClaims(new []
{
new Claim("access_token", requestResponse.AccessToken),
new Claim("id_token", requestResponse.IdentityToken),
new Claim("refresh_token", requestResponse.RefreshToken)
});
// store the refresh_token in the session, as the user might be logged out, when the authorization attribute is executed
// see OrganicaAuthorize.cs
HttpContext.Current.Session["refresh_token"] = requestResponse.RefreshToken;
// get the userinfo from the openId endpoint
// this actually retreives all the claims, but using the normal access token
var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(idServerUserInfoEndpoint, requestResponse.AccessToken); // todo: userinfo
if (userInfo == null) throw new Exception("Could not retreive user information from identity server.");
#region Extract individual claims
// extract claims we are interested in
var nameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name)); // full name
var givenNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName)); // given name
var familyNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName)); // family name
var emailClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email)); // email
var subClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject)); // userid
#endregion
#region Extract roles
List<string> roles;
try
{
roles = userInfo.Value<JArray>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role).Select(r => r.ToString()).ToList();
}
catch (InvalidCastException) // if there is only 1 item
{
roles = new List<string> { userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role) };
}
#endregion
// attach the claims we just extracted
identity.AddClaims(new[] { nameClaim, givenNameClaim, familyNameClaim, subClaim, emailClaim });
// attach roles
identity.AddClaims(roles.Select(r => new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role, r.ToString())));
// update the return value of the SecurityTokenValidated method (this method...)
n.AuthenticationTicket = new AuthenticationTicket(
identity,
n.AuthenticationTicket.Properties);
},
AuthenticationFailed = async n =>
{
System.Diagnostics.Debug.Print("AuthenticationFailed " + n.Exception.ToString());
},
MessageReceived = async n =>
{
System.Diagnostics.Debug.Print("MessageReceived " + n.State.ToString());
},
SecurityTokenReceived = async n =>
{
System.Diagnostics.Debug.Print("SecurityTokenReceived " + n.State.ToString());
},
SecurityTokenValidated = async n =>
{
System.Diagnostics.Debug.Print("SecurityTokenValidated " + n.State.ToString());
}
}
});
您是否在MVC应用中配置了cookie认证中间件?在使用身份服务器进行身份验证后,应设置一个身份验证 cookie。当设置了身份验证 cookie 并且在 cookie expires/deleted.
之前不会发生有效的 IdentityServer 重定向
更新 1:
好的,我误解了问题。当会话超时时重定向到身份服务器是合乎逻辑的。它不适用于 post 负载。您可以尝试执行以下操作。
- 如果请求是正常的 post,再次将用户重定向到表单
填写页面。
- 如果请求是 ajax post、return 未经授权的结果并且基于
该响应从 javascript.
刷新页面
无论如何,我认为您无法保留 posted 数据,除非您为此设计自己的解决方案。 (例如,将数据存储在本地)。
但如果您仔细决定身份服务器的会话超时和您的应用程序的会话超时,您也许能够完全避免这种情况。
在 OpenIdConnectAuthenticationOptions
中设置 UseTokenLifetime = false
这将断开身份令牌生命周期和 cookie 会话生命周期之间的联系。
在CookieAuthenticationOptions
做滑动过期
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(50),
现在您可以控制应用程序的会话生命周期。调整它以满足您的需求和安全考虑。
TL;DR
如何在 ASP.NET MVC 项目(表单,jQuery,axios)中使用 IdentityServer3 作为身份验证服务器来 POST 数据。另外,要使用什么流程才能完成这项工作?
我正在经历的事情
我有一个可用的 IdentityServer3 实例。我还有一个 ASP.NET MVC 项目。使用混合流,因为我必须将用户的令牌传递给其他服务。身份验证本身有效 - 当页面仅使用 GET 时。即使经过身份验证的用户的令牌已过期,后台也会将请求重定向到 auth。服务器,用户可以继续它的工作,而无需要求用户再次登录。 (据我所知,混合流程可以使用刷新令牌,所以我认为这就是它可以重新验证用户身份的方式。即使 HttpContext.Current.User.Identity.IsAuthenticated=false
)
出于测试目的,我在身份验证中将 AccessTokenLifetime
、AuthorizationCodeLifetime
和 IdentityTokenLifetime
值设置为 5 秒。服务器。据我所知,刷新令牌的过期时间以天为单位,我没有更改默认值。
但是当我尝试使用 POST 时,事情变得 "ugly"。
- 使用表单 POST,使用过期的令牌,请求被重定向到 IdentityServer3。它确实很神奇(用户获得身份验证)并重定向到我的页面 - 作为 GET 请求...我在 URL 中看到
response_mode=form_post
,但发布的有效负载消失了。 - 使用 axios POST,请求被重定向到 IdentityServer3,但在飞行前的 OPTIONS 请求中失败。
- 使用默认的 jQuery POST,得到同样的错误。 (尽管如此,默认的 jQuery POST 使用
application/x-www-form-urlencoded
来解决飞行前问题。)
startup.cs
const string authType = "Cookies";
// resetting Microsoft's default mapper
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
// ensure, that the MVC anti forgery key engine will use our "custom" user id
AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = authType
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
RedirectUri = adminUri,
PostLogoutRedirectUri = adminUri,
Authority = idServerIdentityEndpoint,
SignInAsAuthenticationType = authType,
ResponseType = "code id_token",
Scope = "openid profile roles email offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
#region Handle automatic redirect (on logout)
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType ==
OpenIdConnectRequestType.LogoutRequest)
{
var token = n.OwinContext.Authentication.User.FindFirst(idTokenName);
if (token != null)
{
var idTokenHint =
token.Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
},
#endregion
AuthorizationCodeReceived = async n =>
{
System.Diagnostics.Debug.Print("AuthorizationCodeReceived " + n.ProtocolMessage.ToString());
// fetch the identity from authentication response
var identity = n.AuthenticationTicket.Identity;
// exchange the "code" token for access_token, id_token, refresh_token, using the client secret
var requestResponse = await OidcClient.CallTokenEndpointAsync(
new Uri(idServerTokenEndpoint),
new Uri(adminUri),
n.Code,
clientId,
clientSecret
);
// fetch tokens from the exchange response
identity.AddClaims(new []
{
new Claim("access_token", requestResponse.AccessToken),
new Claim("id_token", requestResponse.IdentityToken),
new Claim("refresh_token", requestResponse.RefreshToken)
});
// store the refresh_token in the session, as the user might be logged out, when the authorization attribute is executed
// see OrganicaAuthorize.cs
HttpContext.Current.Session["refresh_token"] = requestResponse.RefreshToken;
// get the userinfo from the openId endpoint
// this actually retreives all the claims, but using the normal access token
var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(idServerUserInfoEndpoint, requestResponse.AccessToken); // todo: userinfo
if (userInfo == null) throw new Exception("Could not retreive user information from identity server.");
#region Extract individual claims
// extract claims we are interested in
var nameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Name)); // full name
var givenNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName)); // given name
var familyNameClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName)); // family name
var emailClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Email)); // email
var subClaim = new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject,
userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject)); // userid
#endregion
#region Extract roles
List<string> roles;
try
{
roles = userInfo.Value<JArray>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role).Select(r => r.ToString()).ToList();
}
catch (InvalidCastException) // if there is only 1 item
{
roles = new List<string> { userInfo.Value<string>(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role) };
}
#endregion
// attach the claims we just extracted
identity.AddClaims(new[] { nameClaim, givenNameClaim, familyNameClaim, subClaim, emailClaim });
// attach roles
identity.AddClaims(roles.Select(r => new Claim(Thinktecture.IdentityModel.Client.JwtClaimTypes.Role, r.ToString())));
// update the return value of the SecurityTokenValidated method (this method...)
n.AuthenticationTicket = new AuthenticationTicket(
identity,
n.AuthenticationTicket.Properties);
},
AuthenticationFailed = async n =>
{
System.Diagnostics.Debug.Print("AuthenticationFailed " + n.Exception.ToString());
},
MessageReceived = async n =>
{
System.Diagnostics.Debug.Print("MessageReceived " + n.State.ToString());
},
SecurityTokenReceived = async n =>
{
System.Diagnostics.Debug.Print("SecurityTokenReceived " + n.State.ToString());
},
SecurityTokenValidated = async n =>
{
System.Diagnostics.Debug.Print("SecurityTokenValidated " + n.State.ToString());
}
}
});
您是否在MVC应用中配置了cookie认证中间件?在使用身份服务器进行身份验证后,应设置一个身份验证 cookie。当设置了身份验证 cookie 并且在 cookie expires/deleted.
之前不会发生有效的 IdentityServer 重定向更新 1:
好的,我误解了问题。当会话超时时重定向到身份服务器是合乎逻辑的。它不适用于 post 负载。您可以尝试执行以下操作。
- 如果请求是正常的 post,再次将用户重定向到表单 填写页面。
- 如果请求是 ajax post、return 未经授权的结果并且基于 该响应从 javascript. 刷新页面
无论如何,我认为您无法保留 posted 数据,除非您为此设计自己的解决方案。 (例如,将数据存储在本地)。
但如果您仔细决定身份服务器的会话超时和您的应用程序的会话超时,您也许能够完全避免这种情况。
在 OpenIdConnectAuthenticationOptions
中设置 UseTokenLifetime = false
这将断开身份令牌生命周期和 cookie 会话生命周期之间的联系。
在CookieAuthenticationOptions
做滑动过期
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(50),
现在您可以控制应用程序的会话生命周期。调整它以满足您的需求和安全考虑。