防伪标记过期空白页

12

我正在使用IdentityServer4和ASP.NET Core 2.2。在Post Login方法中,我已经使用了ValidateAntiForgeryToken。通常在登录页面上坐了20分钟到2小时后尝试登录时,它会产生一个空白页面。

如果你查看Postman控制台,你会得到一个400 Bad Request消息。然后我将AntiForgery选项的Cookie过期时间设置为90天。我能够让页面保持登录状态长达6小时,仍然能够登录。然而,在大约8个小时(过夜)之后,我再次尝试登录时又收到了空白页面。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login
services.AddAntiforgery(options =>
{
    options.Cookie.Expiration = TimeSpan.FromDays(90);
});

我希望能够在登录页面停留90天,这是cookie的持续时间,但这并不起作用。 我该如何使AntiforgeryToken的cookie持续整个90天或我设置的任何时间而不超时或过期? 是否有一种方法捕获此错误并将用户重定向回登录方法?


1
不仅有cookie,还有一个token。微软在设置它的ttl方面有一个开放的问题(https://github.com/aspnet/AspNetCore/issues/2421)。但是据我所知,按照默认设置,它应该与浏览器会话保持同步...但您可以通过他们的开放代码进行检查:) - d_f
关于更改行为:您可以覆盖过滤器并重定向到具有相同名称的操作,但使用HttpGet而不是返回400状态。 - d_f
我能够通过使用以下代码来控制验证失败后的结果:await this._antiforgery.IsRequestValidAsync(HttpContext);。目前这对我非常有效,而不是使用内置属性。 - Rodney Bates
很高兴它对你有用,但通常最好在任何地方使用一些常见的方法,然后通过将您的实现包装成一个新属性来完成作业,然后在下面描述它作为答案,以供其他寻找类似解决方案的人使用。 - d_f
为什么你需要使用AntiForgeryToken?在这里,你正在打败AntiForgeryToken的目的。与其拥有一个持续时间如此之长的令牌,不如一开始就没有它更好。 - Mat J
4个回答

7

更新 '2021

自 ASP.Net Core 3.0 以来,微软 决定ValidateAntiforgeryTokenAuthorizationFilter 设为内部。现在我们不得不复制粘贴 他们的代码,才能够派生。但最有可能的是我们并不需要这样做。只需测试上下文是否存在 IAntiforgeryValidationFailedResult 并据此进行相应操作,如本 示例 中所述,即可更改其结果行为。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Core.Infrastructure;
using Microsoft.AspNetCore.Mvc.Filters;

namespace BasicWebSite.Filters
{
    public class RedirectAntiforgeryValidationFailedResultFilter : IAlwaysRunResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            if (context.Result is IAntiforgeryValidationFailedResult result)
            {
                context.Result = 
                    new RedirectResult("http://example.com/antiforgery-redirect");
            }
        }

        public void OnResultExecuted(ResultExecutedContext context)
        { }
    }
}

然后在控制器中:

// POST: /Antiforgery/LoginWithRedirectResultFilter
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[TypeFilter(typeof(RedirectAntiforgeryValidationFailedResultFilter))]
public string LoginWithRedirectResultFilter(LoginViewModel model)
{
    return "Ok";
}

涵盖 .net core 2.2 的原始答案

采用默认实现,包括所有预检查、日志记录等。它仍然是一个AuthorizationFilter,这样可以阻止任何进一步的操作执行。唯一的区别是它触发了HttpGet到相同的url,而不是默认的400响应,一种Post/Redirect/Get模式的实现。

public class AnotherAntiForgeryTokenAttribute : TypeFilterAttribute
{
    public AnotherAntiForgeryTokenAttribute() : base(typeof(AnotherAntiforgeryFilter))
    {
    }
}


public class AnotherAntiforgeryFilter:ValidateAntiforgeryTokenAuthorizationFilter,
    IAsyncAuthorizationFilter
{
    public AnotherAntiforgeryFilter(IAntiforgery a, ILoggerFactory l) : base(a, l)
    {
    }

    async Task IAsyncAuthorizationFilter.OnAuthorizationAsync(
        AuthorizationFilterContext ctx)
    {
        await base.OnAuthorizationAsync(ctx);

        if (ctx.Result is IAntiforgeryValidationFailedResult)
        {
            // the next four rows are optional, just illustrating a way
            // to save some sensitive data such as initial query
            // the form has to support that
            var request = ctx.HttpContext.Request;
            var url = request.Path.ToUriComponent();
            if (request.Form?["ReturnUrl"].Count > 0)
                url = $"{url}?ReturnUrl={Uri.EscapeDataString(request.Form?["ReturnUrl"])}";

            // and the following is the only real customization
            ctx.Result = new LocalRedirectResult(url);
        }
    }
}

我将测试这个并稍后与您联系。由于存在风险,我们决定在验证失败时不使用ReturnUrl。现在我只是返回一个带有消息的视图。 - Rodney Bates
1
这个很好用。我会采用你的解决方案,因为它使用了默认的防伪验证。感谢你的帮助。 - Rodney Bates
请问ValidateAntiforgeryTokenAuthorizationFilter的命名空间是什么?我正在使用asp.net core 5,但似乎找不到这个类。需要哪个“Using”语句? - Josh
事情有所改变。更新了答案。 - d_f
谢谢您的更新。但现在,我该如何将其应用于 Razor 页面?因为我需要将其应用于登录页面,该页面是 Razor 页面,而且似乎(Antiforgery)不能像应用于 MVC 控制器那样应用于 Razor 页面。 - Josh
根据这篇文章,您可以在Razor页面中使用相同的属性。因此,只需创建自己的属性,而不是他们建议的IgnoreAntiforgeryToken - d_f

4
这是我的最终解决方案。我使用IAntifogery依赖注入添加了一个属性。
public class CustomValidationAttribute : ActionFilterAttribute
{
    private IAntiforgery _antiForgery { get; }

    public CustomValidationAttribute(IAntiforgery antiforgery)
    {
        _antiForgery = antiforgery;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var isRequestValid = await this._antiForgery.IsRequestValidAsync(context.HttpContext);
        if (!isRequestValid)
        {
            //Add Code here if token is not valid

            return;         
        }

        await next();
    }
}

在使用 [HttpPost] 的控制器方法中,添加该属性。
[TypeFilter(typeof(CustomValidationAttribute))]

我给了你我的投票并添加了一个带有覆盖的替代方案。可能会更简单一些,但总的来说这并不重要。 - d_f

0
我们正在使用.netcore 3.1/razor pages,并希望将错误放入模型状态而不是返回400错误请求。根据Rodney和Petr J的答案,我们首先全局禁用了验证(因为自动验证默认开启),然后使用页面过滤器进行手动验证。虽然不是最高效的方法,但比尝试捕获和恢复AntiforgeryValidationFailedResult更直接明了。
配置服务:
services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            // Note: This seems to apply globally to all razor areas vs .AddRazorPages()
            
            // First remove default validation which returns 400 Bad Request
            //https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#overriding-the-default-order
            options.Filters.Add(new Microsoft.AspNetCore.Mvc.IgnoreAntiforgeryTokenAttribute() { Order = 1001 });

            // Then add back a page filter, runs on all pages and areas (not tested on anything except razor pages; confirm odata, api, mvc)
            // Option 1 Type activated: instance for each request, DI
            // https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#dependency-injection
            options.Filters.Add(typeof(Filters.AntiforgeryValidationModelStateOverridePageFilter));
            
        })
            

然后是自定义过滤器:

public class AntiforgeryValidationModelStateOverridePageFilter : IAsyncPageFilter {

    private readonly IAntiforgery _defaultAntiforgery;
    public AntiforgeryValidationModelStateOverridePageFilter(IAntiforgery defaultAntiforgery)
    {
        _defaultAntiforgery = defaultAntiforgery;
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        var isRequestValid = _defaultAntiforgery.IsRequestValidAsync(context.HttpContext).Result;

        if (!isRequestValid)
        {
            //Add Code here if token is not valid
            context.ModelState.AddModelError("", Data.Constants.UserMessages.UserMessageErrorAntiforgery);
        }

        return Task.CompletedTask;
    }

    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
                                                  PageHandlerExecutionDelegate next)
    {
        // Do post work.
        await next.Invoke();
    }
}

0
稍微修改了d_f的代码https://dev59.com/GFMI5IYBdhLWcg3waqs4#56383473,我们不再进行页面重定向,而是将错误添加到ModelState中。然后我们在模型状态摘要中显示错误信息。
public class CustomAntiForgeryTokenAttribute : TypeFilterAttribute
{
    public CustomAntiForgeryTokenAttribute() : base(typeof(AnotherAntiforgeryFilter))
    {
    }
}


public class AnotherAntiforgeryFilter : ValidateAntiforgeryTokenAuthorizationFilter,
    IAsyncAuthorizationFilter
{
    public AnotherAntiforgeryFilter(IAntiforgery a, ILoggerFactory l) : base(a, l)
    {
    }

    async Task IAsyncAuthorizationFilter.OnAuthorizationAsync(
        AuthorizationFilterContext ctx)
    {
        await base.OnAuthorizationAsync(ctx);

        if (ctx.Result is IAntiforgeryValidationFailedResult)
        {
            ctx.ModelState.AddModelError("Token", "Validation Token Expired. Please try again");
            ctx.Result = null;

        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接