Как вернуть 401 вместо 302 в ASP.NET Core?

Я пытаюсь заставить ASP.NET Core Identity возвращать 401, когда пользователь не вошел в систему. Я добавил атрибут [Authorize] в свой метод, и вместо того, чтобы возвращать 401, он возвращает 302. Я пробовал массу предложений но ничего не работает, включая services.Configure и app.UseCookieAuthentication установку LoginPath на null или PathString.Empty.


person Eric B    schedule 06.08.2016    source источник
comment
Для всех, кто идет по тому же пути, я попал сюда из devblog.dymel.pl/2016/07/07/   -  person Brett Rossier    schedule 21.01.2019


Ответы (8)


Начиная с ASP.NET Core 2.x:

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = 401;    
        return Task.CompletedTask;
    };
});
person Matthew Steven Monkan    schedule 24.07.2017
comment
кроме того, ваш звонок в службу поддержки. AddIdentity должен предшествовать этому - person Eric B; 24.08.2017
comment
Я считаю, что ответ Кроатти должен быть принятым. Вышеупомянутый подход вызовет проблемы, если вы хотите, чтобы запросы ajax получали 401, но запросы, отличные от ajax, перенаправлялись. Вы можете добавить логику для обработки этого, согласно ответу Марка Перри, но эта логика уже встроена в структуру для запросов с заголовком X-Requested-With: XMLHttpRequest. - person Daniel; 01.05.2018
comment
Я написал три разных пользовательских политики для своего проекта. Итак, вернет ли это 401 вместо 302 для всех моих политик? - person KaraKaplanKhan; 30.07.2018
comment
Это сработало. Какая досадная особенность фреймворка. Если бы нам нужен редирект, мы бы указали его ... - person kovac; 09.02.2019
comment
Для меня нужно было установить OnRedirectToAccessDenied. - person Steven Pena; 26.04.2019
comment
Стивен Пена на самом деле прав. Вы не используете 401 перенаправление входа, иначе ваша страница входа не будет работать. На мой взгляд, OnRedirectToAccessDenied - правильный ответ - person Jeff; 18.08.2020

Если заголовок запроса содержит 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/CookieLore.Authentication.Cookies/Events/CookieLore.Authentication.Cookies/Events/CookieLoreAuthentication

person kroatti    schedule 13.02.2018
comment
это должен быть самый простой ответ, не повредивший вашей кодовой базе. Seeh, почему я не нашел тебя раньше. - person Jeff; 28.06.2018
comment
Это не очень полезный ответ, так как он требует, чтобы вызывающая сторона вводила собственное значение заголовка. Как насчет того, чтобы исправить код обработчика, чтобы вместо этого действительно соблюдался заголовок Accept :? Мой клиентский код (angular) отправляет Accept: application / json, но возвращает дерьмовый HTML в качестве ответа, который он не может проанализировать. Код на стороне сервера ЯВЛЯЕТСЯ проблемой. - person theta-fish; 22.01.2019

Для ядра asp.net mvc ИСПОЛЬЗУЙТЕ ЭТО ВМЕСТО

 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;
            };
        });
person Francis Ofosu    schedule 11.11.2017
comment
Это решение отлично работает в Core 2! Спасибо - person Lukas Dvorak; 07.03.2018
comment
В ASP.NET Core 2.0 вы можете использовать return Task.CompletedTask; вместо return Task.FromResult<object>(null); - person SerjG; 11.04.2018
comment
Как упоминалось в другом месте, убедитесь, что это происходит после вызова services.AddIdentity(…) или AddDefaultIdentity(…) - person Jamie F; 12.09.2018
comment
Спасибо, отлично работает. Однако есть ли способ вернуть 401 и json (что-то вроде {authorize: Failed}) - person howardlo; 25.01.2019
comment
Идеально! Работал как шарм - person Jeff; 18.08.2020

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);
      }
   };
});

Источник:

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

person Mark Perry    schedule 23.01.2017
comment
Это именно то, что мне нужно. Спасибо! - person Jeff Yates; 19.06.2017
comment
Отлично работает на ядре .net 2.1 - хотя мне пришлось изменить StartsWith (/ api) на contains (api), но после этого он отлично работал. Думаю, мне нужно что-то сделать в моем приложении сейчас, чтобы обнаруживать 401 и соответственно перенаправлять после любого HTTP-запроса. Ваше здоровье! - person DanAbdn; 04.11.2018
comment
Для меня нужно было установить OnRedirectToAccessDenied. - person Steven Pena; 26.04.2019

Хорошо, после того, как покопался в aspNetCore.Identity.Test/IdentityOptionsTest.cs " основные модульные тесты Я наконец нашел рабочее решение. Вы должны добавить следующее к своему вызову в services.AddIdentity

services.AddIdentity<ApplicationUser, IdentityRole>(o => {
    o.Cookies.ApplicationCookie.AutomaticChallenge = false;
});
person Eric B    schedule 06.08.2016
comment
Это по-прежнему перенаправляет пользователя на страницу входа? Я получаю МНОГО трафика на страницы, которые при нажатии будут перенаправлены на страницу входа из-за 302. Я все еще хочу, чтобы это работало, но я хочу, чтобы все боты не выполняли этот вызов, чтобы они никогда его не вызывали. снова (401/403). 302 по-прежнему скажет им продолжать звонить. (Это для тех, кто не поддерживает файл robots.txt, где я специально запрещаю этот шаблон URL) - person ganders; 27.12.2016
comment
@ganders Нет. Если вы хотите, чтобы ваше приложение работало определенным образом на 401, вам придется справиться с этим самостоятельно. Обычно 401 должен включать заголовок WWW-Authenticate, который описывает, как аутентифицироваться. - person Eric B; 27.12.2016
comment
WWW-Authenticate заголовок ДОЛЖЕН быть предоставлен на 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 - person urbanhusky; 17.05.2017
comment
Кажется, этого нет (больше) в .NET Core 3.x (предварительная версия): / - person Bernoulli IT; 01.08.2019

Для ASP.NET Core 3.x (предварительная версия) с использованием аутентификации Identity with Cookie вот что помогло:

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;
    };
});

Это то, что мы видим повсюду в разных вариациях. НО, здесь важно указать ConfigureApplicationCookie ПОСЛЕ AddIdentity. Это «грустно», но факт. Этот ТАК-ответ наконец-то пролил свет во тьме.

Я уже больше суток чешу затылок и перепробовала много разных вариантов:

  • Переопределить атрибут авторизации (больше не нужно переопределять в 3.x)
  • Указание options.Cookie.EventType с помощью Cookie (ошибка времени выполнения)
  • options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme (было сказано, что носитель JWT не будет перенаправлять на страницу входа)
  • И, конечно же, настройка ApplicationCookie (но перед вызовом AddIdentity, который не работает.

Все это не сработало. Но с ответом выше я наконец получил возвращенное сообщение 401 Unauthorized (которое, кстати, должно быть Unauthenticated).

person Bernoulli IT    schedule 01.08.2019
comment
Нет необходимости устанавливать заголовок местоположения - это полезно только для кода состояния 3xx - person Andy; 27.02.2020
comment
Привет, @Bernoulli, я использую asp.net core 3.1, пытался поместить аналогичный код в клиентское приложение, но это не сработало. - person MayankGaur; 12.05.2020
comment
Что не сработало? Он не компилировался? Были исключения? Что за исключение / сообщение? Основной момент этого ответа - это порядок, в котором объявляются вещи, которые решили проблему OP (и мою). - person Bernoulli IT; 12.05.2020
comment
В моем случае это тоже не сработало. Если я поставлю точку останова в функции OnRedirectToLogin или OnRedirectToAccessDenied, она никогда не сломается. Я не могу понять, как заставить это работать в .NET Core 3.1. Моя цель - иметь возможность перенаправить на страницу входа или вернуть статус 401 в зависимости от того, был ли вызываемый метод методом API или веб-страницей. - person killswitch; 29.03.2021

Для меня в 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;
            };
        }
    );
person SirGordon    schedule 17.05.2019

В продолжение, я объединил предыдущие ответы в следующие:

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);
        }

    }
person Damian Renz'es    schedule 30.09.2019