.NET 5 - Cookie 验证挑战 rewrite/re-execute 而不是重定向

.NET 5 - Cookie authetication on challenge rewrite/re-execute instead of redirect

我们目前正在将 .NET Framework 4.8 MVC 应用程序迁移到 .NET (Core) 5,我遇到了一个挑战。

在旧框架应用程序中,我们实现了 AuthorizeAttribute,其中对 HandleUnauthorizedRequest 的覆盖将清除响应的内容并 're'- 执行另一个控制器(借助 IControllerFactory).这实质上创建了一个登录登录页面,其中 URL 保持不变(相同的请求)。对于具有此属性的每个安全页面,我们都会呈现一个不同的页面(如您所愿,'sales pitch')。

现在,在 .NET Core 中,身份验证的工作方式与在 .NET Framework 中不同。所以我相信前面描述的方法在这里行不通。我认为正确的方法是配置 cookie 身份验证来处理这个问题。但是我们有两个业务需求使这有点挑战:

  1. 没有重定向;相反,在第一次请求时显示登录页面,所以基本上,URL rewrite/re-execute 而不是重定向
  2. 每个需要身份验证的页面都应呈现不同的登录页面

我的挑战在于第一个要求。我需要找到一种方法来在身份验证时呈现不同的控制器操作。 URL 重新执行也可以,因为我们已经在自定义 UseStatusCodePages 处理程序中这样做了。 AddAuthentication().AddCookie(o => o.Events.OnRedirectToLogin = context => { ... } 看起来像是我可以处理我的逻辑的地方,但我尝试过的任何东西都不起作用。原因可能是中间件太远了?

所以问题是:如何重写或重新执行身份验证质询而不是重定向?

在同事的帮助下,通过查看 UseStatusCodePagesUseRewriter 功能的源代码,我们找到了解决问题的办法。

我们最终在 AddCookie 上实施了 OnRedirectToLogin 事件。当这个事件被触发时,我们找到我们需要显示的当前登录页面的 URI。但是我们没有设置 context.RedirectUri,而是将其添加到 context.HttpContext.Items 字典中。然后我们使用自定义中间件来查找该项目并在那里执行 rewrite/re-execute 。 return Task.CompletedTask; 确保没有任何反应,没有重定向。没有中间件,我们得到一个空白页面。

services.AddAuthentication().AddCookie(o =>
{
    o.Events.OnRedirectToLogin = context =>
    {
        var loginPageUri = GetLoginLandingPageUri(context);

        context.HttpContext.Items[ReExecuteUriMiddleware.ReExecuteUriItemKey] = loginPageUri;

        return Task.CompletedTask;
    };
});

当中间件在 Items 字典中看到该项目时,我们会重置端点信息,因此当我们第二次调用 _next 时,中间件管道会很好地 re-execute 请求,但是有不同的路径。

public class ReExecuteUriMiddleware
{
    public const string ReExecuteUriItemKey = "ReExecuteUri";
    private readonly RequestDelegate _next;

    public ReExecuteUriMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        await _next.Invoke(httpContext);

        if (httpContext.Items[ReExecuteUriItemKey] is string reExecuteUri)
        {
            // An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
            // the endpoint and route values to ensure things are re-calculated.
            httpContext.SetEndpoint(null);
            var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
            routeValuesFeature?.RouteValues?.Clear();

            httpContext.Request.Path = reExecuteUri;

            await _next.Invoke(httpContext);
        }
    }
}

当然我们在 Startup class app.UseStatusCodePages() 之后添加了它。

app.UseMiddleware<ReExecuteUriMiddleware>();