如何在ASP.NET Core中返回401而不是302?

40

我想让ASP.NET Core Identity在用户未登录时返回401。我已经在我的方法上添加了[Authorize]特性,但它返回302而不是401。我尝试了很多建议,包括使用services.Configureapp.UseCookieAuthenticationLoginPath设置为nullPathString.Empty,但似乎都没有起作用。


对于其他跟随这条路线的人,我是从https://devblog.dymel.pl/2016/07/07/return-401-unauthorized-from-asp-net-core-api/到达这里的。 - Brett Rossier
8个回答

68

截至ASP.NET Core 2.x

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = 401;    
        return Task.CompletedTask;
    };
});

3
另外,您调用services.AddIdentity的操作必须在此之前执行。 - Eric B
4
我认为kroatti的答案应该被接受。如果你希望ajax请求得到401而非ajax请求得到重定向,上述方法会导致问题。你可以按照Mark Perry的回答添加处理逻辑,但对于带有X-Requested-With: XMLHttpRequest头的请求,这种逻辑已经内置于框架中了。 - Daniel
我为我的项目编写了三个不同的自定义策略。那么,这是否会对我所有的策略都返回401而不是302? - KaraKaplanKhan
4
这个方案有效。框架中的这个功能太烦人了。如果我们需要重定向,我们会指定它的... - kovac
对我来说,需要设置的是OnRedirectToAccessDenied。 - Steven Pena
Steven Pena是正确的。您不应该将登录重定向到401,否则您的登录页面将无法正常工作。在我看来,OnRedirectToAccessDenied才是正确的答案。 - Jeff

24

如果请求头中包含 X-Requested-With: XMLHttpRequest,那么状态码将会是401而不是302

private static bool IsAjaxRequest(HttpRequest request)
    {
        return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
            string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
    }

在 GitHub 上查看:https://github.com/aspnet/Security/blob/5de25bb11cfb2bf60d05ea2be36e80d86b38d18b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs#L40-L52


2
这应该是最简单直接的答案,不会影响您的代码库。看到你这么优秀,我为什么没早点找到你呢。 - Jeff
7
这并不是一个很有帮助的答案,因为它要求调用者注入一个专有标题值。修复处理程序代码以实际尊重“Accept:”标头如何?我的客户端代码(Angular)正在发送“Accept: application/json”,但收到不能解析的垃圾HTML作为响应。 服务器端代码存在问题。 - theta-fish

17
services.Configure<IdentityOptions>(options =>
{
   options.Cookies.ApplicationCookie.LoginPath = new PathString("/");
   options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
   {
      OnRedirectToLogin = context =>
      {
         if (context.Request.Path.Value.StartsWith("/api"))
         {
            context.Response.Clear();
            context.Response.StatusCode = 401;
            return Task.FromResult(0);
         }
         context.Response.Redirect(context.RedirectUri);
         return Task.FromResult(0);
      }
   };
});

Source:

https://www.illucit.com/blog/2016/04/asp-net-5-identity-302-redirect-vs-401-unauthorized-for-api-ajax-requests/


这正是我所需要的。谢谢! - Jeff Yates
在 .net core 2.1 上运行得非常好 - 尽管我不得不将 StartsWith("/api") 更改为 contains("api"),但之后运行得非常好。现在我需要在我的应用程序中进行一些操作,以便在任何 http 请求后检测到 401 并相应地进行重定向。干杯! - DanAbdn
对我来说,需要设置的是OnRedirectToAccessDenied。 - Steven Pena

15

对于 asp.net mvc core 请使用此方法替代

 services.ConfigureApplicationCookie(options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToLogin = context =>
            {
                if (context.Request.Path.StartsWithSegments("/api")
                    && context.Response.StatusCode == StatusCodes.Status200OK)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        });

这个解决方案在Core 2上完美运行!谢谢。 - Lukas Dvorak
3
使用 ASP.NET Core 2.0,你可以使用 return Task.CompletedTask; 替代 return Task.FromResult<object>(null); - SerjG
如其他地方所提到的,请确保此代码位于调用 services.AddIdentity(…)AddDefaultIdentity(…) 之后。 - Jamie F
2
谢谢,这个很好用。不过,有没有一种方法可以返回401和json(类似于{ authorize:“失败”})? - howardlo
完美!像魔法一样运行。 - Jeff

5
好的,在查找了asp.net core单元测试后,我终于找到了一个可行的解决方案。您需要在调用services.AddIdentity时添加以下内容。
services.AddIdentity<ApplicationUser, IdentityRole>(o => {
    o.Cookies.ApplicationCookie.AutomaticChallenge = false;
});

这个还会重定向用户到登录页面吗?我有很多流量访问那些页面,但是当点击时,因为302而重定向到登录页面。我仍然希望它能正常工作,但我希望所有机器人都无法调用该页面(401/403)。302仍然会告诉它们继续调用。 (这是针对那些不遵守robots.txt文件的机器人,我在其中明确“禁止”该URL模式) - ganders
1
@ganders 不行。如果你想让你的应用程序在401时以特定方式运行,你必须自己处理它。通常,401应该包含一个WWW-Authenticate头,描述如何进行身份验证。 - Eric B
WWW-Authenticate header MUST be provided on 401.If the protected resource request does not include authentication credentials or does not contain an access token that enables access to the protected resource, the resource server MUST include the HTTP "WWW-Authenticate" response header field - urbanhusky
这似乎在 .NET Core 3.x(预览版)中不再存在:/ - Youp Bernoulli

4

对于使用Identity和Cookie身份验证的ASP.NET Core 3.x(预览版),这就是解决方法:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<IdentityContext>()
    .AddDefaultTokenProviders()
    .AddRoles<IdentityRole>();

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.Headers["Location"] = context.RedirectUri;
        context.Response.StatusCode = 401;
        return Task.CompletedTask;
    };
});

这是我们在不同变体中到处看到的东西,但是,这里的关键点是必须在AddIdentity之后指定ConfigureApplicationCookie。这是“悲伤”但却是事实。这个SO答案终于给黑暗带来了光明。
我已经想破头皮尝试多种不同的方法一天了:
- 重写Authorize特性(在3.x中不太需要重写) - 使用Cookie指定options.Cookie.EventType(运行时错误) - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme(据说JWT承载者不会重定向到登录页面) - 当然还包括配置ApplicationCookie(但在调用AddIdentity之前不起作用)
所有这些都没有奏效。但是有了上面的答案,我终于得到了401未授权返回(顺便说一下,应该是未经身份验证

1
不需要设置“location”头 - 这只对3xx状态码有用。 - Andy
嗨@Bernoulli,我正在使用asp.net core 3.1,在客户端应用程序中尝试放置类似的代码,但它没有起作用。 - MayankGaur
什么没有起作用?它没有编译吗?它抛出了异常吗?是什么类型的异常/消息?这个答案的主要观点是声明“事物”的顺序解决了OP(和我)的问题。 - Youp Bernoulli
在我的情况下,它也没有起作用。如果我在OnRedirectToLogin或OnRedirectToAccessDenied函数中设置断点,它永远不会中断。我无法弄清楚如何在.NET Core 3.1上使其工作。我的目标是能够根据调用的方法是API方法还是网页来重定向到登录页面或返回状态401。 - killswitch

1
继续上文,我将之前的答案合并为以下内容:

1. Startup.cs

services.ConfigureApplicationCookie(options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToAccessDenied = context =>
            {
                if (wlt_AjaxHelpers.IsAjaxRequest(context.Request))
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        });

2. 帮助器自定义类

public static class wlt_AjaxHelpers
     {

        public static bool IsAjaxRequest( HttpRequest request )
        {

            return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
                string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
        }

    }

0

对于我来说,在ASP.NET Core 2.2.0中只有这个有效:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(
        options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToLogin = context =>
            {
                if (context.Request.Path.StartsWithSegments("/api")
                    && context.Response.StatusCode == StatusCodes.Status200OK)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        }
    );

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