ASP.NET POST 在 Identityserver 上进行身份验证后表单数据丢失
ASP.NET POST Form Data is lost after authentication on Identityserver
我有一个 asp.net mvc 项目使用 identityserver4 隐式流作为身份验证方法。我使用的是引用令牌而不是 JWT,因此 MVC 客户端应用程序必须每隔一段时间在服务器上重新授权令牌。
我面临的问题是当我在客户端提交表单并在提交时转到身份验证端点(就在实际提交之前)。
身份验证后,我被发送回我的 URL,但作为 GET。因此 POST 数据丢失,我必须重新填写表格。
我现在要做的是在重定向到 identityserver4 并调整响应以使其再次成为 POST 之前跟踪表单数据:
下面的方法可以调整上下文(使其成为 POST 请求并添加正确的数据),但它仍然作为 GET 重定向回来。
private Dictionary<string, IOwinRequest> tempRequests = new Dictionary<string, IOwinRequest>();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
[removed for brevity...]
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = async (context) =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication && context.Request.Method == HttpMethods.Post)
{
// When it's a POST request, save the request and keep track of it using a Guid reference
string requestId = Guid.NewGuid().ToString();
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("OidcPostRedirectRequestId", requestId);
tempRequests.Add(requestId, context.Request);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
}
},
SecurityTokenValidated = context =>
{
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
if (state.Dictionary.ContainsKey("OidcPostRedirectRequestId"))
{
// Reference found, update request and add form data back to it
state.Dictionary.TryGetValue("OidcPostRedirectRequestId", out string requestId);
if (!string.IsNullOrEmpty(requestId))
{
state.Dictionary.Remove("OidcPostRedirectRequestId");
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
tempRequests.TryGetValue(requestId, out IOwinRequest data);
if (data != null)
{
tempRequests.Remove(requestId);
context.Request.Body = data.Body;
context.Request.ContentType = data.ContentType;
context.Request.Method = data.Method;
context.Request.Headers.Clear();
foreach (var header in data.Headers)
{
context.Request.Headers.Add(header);
}
}
}
}
return Task.FromResult(0);
}
}
});
我是不是走错了路?如有必要,如何使从 Identityserver4 重定向回客户端作为 POST?
提前致谢。
-- 更新 17/07
由于没有成功,我正在尝试通过扩展 OidcAuthorization 中间件来创建自定义重定向处理程序。
public static class OidcAuthenticationExtensions
{
public static readonly string OidcPostRedirectKey = "OidcPostRedirectRequestId";
public static Dictionary<string, IOwinRequest> PostRedirectRequests = new Dictionary<string, IOwinRequest>();
public static IAppBuilder UseKpcOidcAuthentication(this IAppBuilder app, OpenIdConnectAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("openIdConnectOptions");
}
return app.Use(typeof(CustomAuthMiddleware), app, options);
}
}
public class CustomOIDCAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
public CustomOIDCAuthenticationHandler(ILogger logger)
: base(logger) { }
public override Task<bool> InvokeAsync()
{
return InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket != null)
{
if (ticket.Properties.Dictionary.TryGetValue("HandledResponse", out string value) && value == "true")
{
return true;
}
if (ticket.Identity != null)
{
Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
}
// Redirect back to the original secured resource, if any.
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
{
Response.Redirect($"/Helper/RedirectHandler?redirectUrl={ticket.Properties.RedirectUri}");
//Response.Redirect(ticket.Properties.RedirectUri);
return true;
}
}
return false;
}
}
public class CustomAuthMiddleware : OpenIdConnectAuthenticationMiddleware
{
private readonly ILogger _logger;
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, IAppBuilder app, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, app, authOptions)
{
_logger = app.CreateLogger<CustomAuthMiddleware>();
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new CustomOIDCAuthenticationHandler(_logger);
}
}
public async Task<ActionResult> RedirectHandler(string redirectUrl)
{
var claim = (((ClaimsPrincipal)User).FindFirst(OidcAuthenticationExtensions.OidcPostRedirectKey));
if (claim != null)
{
var requestId = claim.Value;
OidcAuthenticationExtensions.PostRedirectRequests.TryGetValue(requestId, out IOwinRequest data);
if (data != null)
{
OidcAuthenticationExtensions.PostRedirectRequests.Remove(requestId);
// TODO: post to action
IFormCollection formData = await data.ReadFormAsync();
//FormData is correct, but I need to be able to post it...
}
}
return Redirect(redirectUrl);
}
如果请求只是 GET,我可以使用它重定向到 URL。但是,当它是 POST 时,我想要 post 数据。关于如何最好地实现这一点有什么想法吗?
在这种情况下,将用户的输入数据发送到不同的(身份验证)服务器以保存它是错误的方向。
OpenID Connect 协议用于互操作性,常见场景是使用第 3 方身份验证服务器(例如 Google、Facebook)。您不会期望他们的服务器在登录过程中保留从客户端发送的任何随机数据。即使使用您自己的服务器,只是“扩展”协议也不是个好主意。那里也存在潜在的隐私问题。
保存表单输入数据应该在应用程序端处理。在客户端可能会更简单,将表单输入数据保存到 localStorage
、cookie 等......或者在服务器端,在 GET 重定向到 OIDC 服务器之前保存会话状态。
这样您就不会干扰 OpenID Connect 协议,应用程序负责保存自己的数据。
对于遇到同样问题的其他人,我通过重写 OpenIdConnectAuthenticationHandler
.
解决了这个问题
我首先做的是实现我自己的自定义 OpenIdConnectAuthenticationMiddleware
,它实现了我的自定义 AuthenticationHandler
。
默认情况下,OpenIdConnectAuthenticationHandler
将始终通过返回一个重定向
302 响应代码,从而导致 POST 数据丢失。
public static class OidcAuthenticationExtensions
{
public static readonly string OidcPostRedirectKey = "OidcPostRedirectRequestId";
public static Dictionary<string, HttpContext> PostRedirectRequests = new Dictionary<string, HttpContext>();
public static IAppBuilder UseKpcOidcAuthentication(this IAppBuilder app, OpenIdConnectAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("openIdConnectOptions");
}
return app.Use(typeof(CustomAuthMiddleware), app, options);
}
}
public class CustomOIDCAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
public CustomOIDCAuthenticationHandler(ILogger logger)
: base(logger) { }
public override Task<bool> InvokeAsync()
{
return InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket != null)
{
if (ticket.Properties.Dictionary.TryGetValue("HandledResponse", out string value) && value == "true")
{
return true;
}
if (ticket.Identity != null)
{
Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
}
// Redirect back to the original secured resource, if any.
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
{
var claim = ((ClaimsIdentity)HttpContext.Current.User.Identity).FindFirst(OidcAuthenticationExtensions.OidcPostRedirectKey);
if (claim != null)
{
var requestId = claim.Value;
OidcAuthenticationExtensions.PostRedirectRequests.TryGetValue(requestId, out HttpContext data);
if (data != null)
{
WebExtensions.RedirectWithData(data.Request, data.Request.RawUrl);
}
((ClaimsIdentity)HttpContext.Current.User.Identity).RemoveClaim(claim);
OidcAuthenticationExtensions.PostRedirectRequests.Remove(requestId);
if (data == null)
{
Response.Redirect(ticket.Properties.RedirectUri);
}
}
else
{
Response.Redirect(ticket.Properties.RedirectUri);
}
return true;
}
}
return false;
}
}
public class CustomAuthMiddleware : OpenIdConnectAuthenticationMiddleware
{
private readonly ILogger _logger;
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, IAppBuilder app, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, app, authOptions)
{
_logger = app.CreateLogger<CustomAuthMiddleware>();
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new CustomOIDCAuthenticationHandler(_logger);
}
}
因此,如果要执行 Post
操作,中间件将使用以下函数重新创建请求:
public static class WebExtensions
{
public static void RedirectWithData(HttpRequest request, string url)
{
HttpContext.Current.Response.Clear();
StringBuilder s = new StringBuilder();
s.Append("<html>");
s.AppendFormat("<body onload='document.forms[\"form\"].submit()'>");
s.AppendFormat("<form name='form' action='{0}' method='post'>", url);
foreach (string key in request.Form)
{
s.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, request.Form[key]);
}
s.Append("</form></body></html>");
HttpContext.Current.Response.Write(s.ToString());
HttpContext.Current.Response.End();
}
}
通过实现这个,我们可以初始化 Startup
class 中的中间件,如下所示:
app.UseKpcOidcAuthentication(new OpenIdConnectAuthenticationOptions
{
[Removed for brevity...]
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = context =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication && context.Request.Method == HttpMethods.Post)
{
// When it's a POST request, save the request and keep track of it using a Guid reference
// Also add the requestId to the request state, so we can retrieve it after authentication on the IdentityServer
string requestId = Guid.NewGuid().ToString();
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add(OidcAuthenticationExtensions.OidcPostRedirectKey, requestId);
OidcAuthenticationExtensions.PostRedirectRequests.Add(requestId, System.Web.HttpContext.Current);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
}
return Task.FromResult(0);
},
SecurityTokenValidated = context =>
{
// Retrieve possible requestId from the state
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
if (state.Dictionary.ContainsKey(OidcAuthenticationExtensions.OidcPostRedirectKey))
{
// Reference found, update request and add form data back to it
state.Dictionary.TryGetValue(OidcAuthenticationExtensions.OidcPostRedirectKey, out string requestId);
if (!string.IsNullOrEmpty(requestId))
{
state.Dictionary.Remove(OidcAuthenticationExtensions.OidcPostRedirectKey);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
// Temporarily add the request id to the user claims, so it can be read in the redirect handler
user.AddClaim(new System.Security.Claims.Claim(OidcAuthenticationExtensions.OidcPostRedirectKey, requestId));
}
}
return Task.FromResult(0);
}
}
});
所以我们现在得到的流量是:
- 用户在客户端提交了一个表单(其中有一个无效的身份令牌)
- 客户端执行
/connect/authorize
端点的重定向以获取新令牌。在实际重定向之前,我们将初始 HttpContext
存储在 Guid
引用的内存中。我们还将此 Guid
添加到 request state
。 (参见 RedirectToIdentityProvider
事件)
- IdentityServer 发出新的
IdenityToken
并重定向回客户端
SecurityTokenValidated
被触发了。我们从 request
中检索引用 Guid
并将其临时添加到 User Claims
.
AuthenticationMiddleware
调用重定向到原始 Uri
。我们检查用户是否有 Claim
引用原始 POST Request
。如果是这样,我们将使用RedirectWithData
函数手动触发Post
;否则只需使用 302. 重定向
我有一个 asp.net mvc 项目使用 identityserver4 隐式流作为身份验证方法。我使用的是引用令牌而不是 JWT,因此 MVC 客户端应用程序必须每隔一段时间在服务器上重新授权令牌。
我面临的问题是当我在客户端提交表单并在提交时转到身份验证端点(就在实际提交之前)。 身份验证后,我被发送回我的 URL,但作为 GET。因此 POST 数据丢失,我必须重新填写表格。
我现在要做的是在重定向到 identityserver4 并调整响应以使其再次成为 POST 之前跟踪表单数据:
下面的方法可以调整上下文(使其成为 POST 请求并添加正确的数据),但它仍然作为 GET 重定向回来。
private Dictionary<string, IOwinRequest> tempRequests = new Dictionary<string, IOwinRequest>();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
[removed for brevity...]
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = async (context) =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication && context.Request.Method == HttpMethods.Post)
{
// When it's a POST request, save the request and keep track of it using a Guid reference
string requestId = Guid.NewGuid().ToString();
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("OidcPostRedirectRequestId", requestId);
tempRequests.Add(requestId, context.Request);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
}
},
SecurityTokenValidated = context =>
{
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
if (state.Dictionary.ContainsKey("OidcPostRedirectRequestId"))
{
// Reference found, update request and add form data back to it
state.Dictionary.TryGetValue("OidcPostRedirectRequestId", out string requestId);
if (!string.IsNullOrEmpty(requestId))
{
state.Dictionary.Remove("OidcPostRedirectRequestId");
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
tempRequests.TryGetValue(requestId, out IOwinRequest data);
if (data != null)
{
tempRequests.Remove(requestId);
context.Request.Body = data.Body;
context.Request.ContentType = data.ContentType;
context.Request.Method = data.Method;
context.Request.Headers.Clear();
foreach (var header in data.Headers)
{
context.Request.Headers.Add(header);
}
}
}
}
return Task.FromResult(0);
}
}
});
我是不是走错了路?如有必要,如何使从 Identityserver4 重定向回客户端作为 POST?
提前致谢。
-- 更新 17/07
由于没有成功,我正在尝试通过扩展 OidcAuthorization 中间件来创建自定义重定向处理程序。
public static class OidcAuthenticationExtensions
{
public static readonly string OidcPostRedirectKey = "OidcPostRedirectRequestId";
public static Dictionary<string, IOwinRequest> PostRedirectRequests = new Dictionary<string, IOwinRequest>();
public static IAppBuilder UseKpcOidcAuthentication(this IAppBuilder app, OpenIdConnectAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("openIdConnectOptions");
}
return app.Use(typeof(CustomAuthMiddleware), app, options);
}
}
public class CustomOIDCAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
public CustomOIDCAuthenticationHandler(ILogger logger)
: base(logger) { }
public override Task<bool> InvokeAsync()
{
return InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket != null)
{
if (ticket.Properties.Dictionary.TryGetValue("HandledResponse", out string value) && value == "true")
{
return true;
}
if (ticket.Identity != null)
{
Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
}
// Redirect back to the original secured resource, if any.
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
{
Response.Redirect($"/Helper/RedirectHandler?redirectUrl={ticket.Properties.RedirectUri}");
//Response.Redirect(ticket.Properties.RedirectUri);
return true;
}
}
return false;
}
}
public class CustomAuthMiddleware : OpenIdConnectAuthenticationMiddleware
{
private readonly ILogger _logger;
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, IAppBuilder app, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, app, authOptions)
{
_logger = app.CreateLogger<CustomAuthMiddleware>();
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new CustomOIDCAuthenticationHandler(_logger);
}
}
public async Task<ActionResult> RedirectHandler(string redirectUrl)
{
var claim = (((ClaimsPrincipal)User).FindFirst(OidcAuthenticationExtensions.OidcPostRedirectKey));
if (claim != null)
{
var requestId = claim.Value;
OidcAuthenticationExtensions.PostRedirectRequests.TryGetValue(requestId, out IOwinRequest data);
if (data != null)
{
OidcAuthenticationExtensions.PostRedirectRequests.Remove(requestId);
// TODO: post to action
IFormCollection formData = await data.ReadFormAsync();
//FormData is correct, but I need to be able to post it...
}
}
return Redirect(redirectUrl);
}
如果请求只是 GET,我可以使用它重定向到 URL。但是,当它是 POST 时,我想要 post 数据。关于如何最好地实现这一点有什么想法吗?
在这种情况下,将用户的输入数据发送到不同的(身份验证)服务器以保存它是错误的方向。
OpenID Connect 协议用于互操作性,常见场景是使用第 3 方身份验证服务器(例如 Google、Facebook)。您不会期望他们的服务器在登录过程中保留从客户端发送的任何随机数据。即使使用您自己的服务器,只是“扩展”协议也不是个好主意。那里也存在潜在的隐私问题。
保存表单输入数据应该在应用程序端处理。在客户端可能会更简单,将表单输入数据保存到 localStorage
、cookie 等......或者在服务器端,在 GET 重定向到 OIDC 服务器之前保存会话状态。
这样您就不会干扰 OpenID Connect 协议,应用程序负责保存自己的数据。
对于遇到同样问题的其他人,我通过重写 OpenIdConnectAuthenticationHandler
.
我首先做的是实现我自己的自定义 OpenIdConnectAuthenticationMiddleware
,它实现了我的自定义 AuthenticationHandler
。
默认情况下,OpenIdConnectAuthenticationHandler
将始终通过返回一个重定向
302 响应代码,从而导致 POST 数据丢失。
public static class OidcAuthenticationExtensions
{
public static readonly string OidcPostRedirectKey = "OidcPostRedirectRequestId";
public static Dictionary<string, HttpContext> PostRedirectRequests = new Dictionary<string, HttpContext>();
public static IAppBuilder UseKpcOidcAuthentication(this IAppBuilder app, OpenIdConnectAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("openIdConnectOptions");
}
return app.Use(typeof(CustomAuthMiddleware), app, options);
}
}
public class CustomOIDCAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
public CustomOIDCAuthenticationHandler(ILogger logger)
: base(logger) { }
public override Task<bool> InvokeAsync()
{
return InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket != null)
{
if (ticket.Properties.Dictionary.TryGetValue("HandledResponse", out string value) && value == "true")
{
return true;
}
if (ticket.Identity != null)
{
Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
}
// Redirect back to the original secured resource, if any.
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
{
var claim = ((ClaimsIdentity)HttpContext.Current.User.Identity).FindFirst(OidcAuthenticationExtensions.OidcPostRedirectKey);
if (claim != null)
{
var requestId = claim.Value;
OidcAuthenticationExtensions.PostRedirectRequests.TryGetValue(requestId, out HttpContext data);
if (data != null)
{
WebExtensions.RedirectWithData(data.Request, data.Request.RawUrl);
}
((ClaimsIdentity)HttpContext.Current.User.Identity).RemoveClaim(claim);
OidcAuthenticationExtensions.PostRedirectRequests.Remove(requestId);
if (data == null)
{
Response.Redirect(ticket.Properties.RedirectUri);
}
}
else
{
Response.Redirect(ticket.Properties.RedirectUri);
}
return true;
}
}
return false;
}
}
public class CustomAuthMiddleware : OpenIdConnectAuthenticationMiddleware
{
private readonly ILogger _logger;
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, IAppBuilder app, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, app, authOptions)
{
_logger = app.CreateLogger<CustomAuthMiddleware>();
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new CustomOIDCAuthenticationHandler(_logger);
}
}
因此,如果要执行 Post
操作,中间件将使用以下函数重新创建请求:
public static class WebExtensions
{
public static void RedirectWithData(HttpRequest request, string url)
{
HttpContext.Current.Response.Clear();
StringBuilder s = new StringBuilder();
s.Append("<html>");
s.AppendFormat("<body onload='document.forms[\"form\"].submit()'>");
s.AppendFormat("<form name='form' action='{0}' method='post'>", url);
foreach (string key in request.Form)
{
s.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, request.Form[key]);
}
s.Append("</form></body></html>");
HttpContext.Current.Response.Write(s.ToString());
HttpContext.Current.Response.End();
}
}
通过实现这个,我们可以初始化 Startup
class 中的中间件,如下所示:
app.UseKpcOidcAuthentication(new OpenIdConnectAuthenticationOptions
{
[Removed for brevity...]
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = context =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication && context.Request.Method == HttpMethods.Post)
{
// When it's a POST request, save the request and keep track of it using a Guid reference
// Also add the requestId to the request state, so we can retrieve it after authentication on the IdentityServer
string requestId = Guid.NewGuid().ToString();
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add(OidcAuthenticationExtensions.OidcPostRedirectKey, requestId);
OidcAuthenticationExtensions.PostRedirectRequests.Add(requestId, System.Web.HttpContext.Current);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
}
return Task.FromResult(0);
},
SecurityTokenValidated = context =>
{
// Retrieve possible requestId from the state
var stateQueryString = context.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = context.Options.StateDataFormat.Unprotect(protectedState);
if (state.Dictionary.ContainsKey(OidcAuthenticationExtensions.OidcPostRedirectKey))
{
// Reference found, update request and add form data back to it
state.Dictionary.TryGetValue(OidcAuthenticationExtensions.OidcPostRedirectKey, out string requestId);
if (!string.IsNullOrEmpty(requestId))
{
state.Dictionary.Remove(OidcAuthenticationExtensions.OidcPostRedirectKey);
context.ProtocolMessage.State = $"{stateQueryString[0]}={context.Options.StateDataFormat.Protect(state)}";
// Temporarily add the request id to the user claims, so it can be read in the redirect handler
user.AddClaim(new System.Security.Claims.Claim(OidcAuthenticationExtensions.OidcPostRedirectKey, requestId));
}
}
return Task.FromResult(0);
}
}
});
所以我们现在得到的流量是:
- 用户在客户端提交了一个表单(其中有一个无效的身份令牌)
- 客户端执行
/connect/authorize
端点的重定向以获取新令牌。在实际重定向之前,我们将初始HttpContext
存储在Guid
引用的内存中。我们还将此Guid
添加到request state
。 (参见RedirectToIdentityProvider
事件) - IdentityServer 发出新的
IdenityToken
并重定向回客户端 SecurityTokenValidated
被触发了。我们从request
中检索引用Guid
并将其临时添加到User Claims
.AuthenticationMiddleware
调用重定向到原始Uri
。我们检查用户是否有Claim
引用原始POST Request
。如果是这样,我们将使用RedirectWithData
函数手动触发Post
;否则只需使用 302. 重定向