Чому AuthorizeAttribute перенаправляє на сторінку входу для помилок аутентифікації та авторизації?


265

У ASP.NET MVC ви можете позначити метод контролера за допомогою AuthorizeAttributeтакого:

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

Це означає, що, якщо користувач, який зараз увійшов, не перебуває в ролі "CanDeleteTags", метод контролера ніколи не буде викликаний.

На жаль, для відмов AuthorizeAttributeповертається HttpUnauthorizedResult, який завжди повертає код статусу HTTP 401. Це спричиняє перенаправлення на сторінку входу.

Якщо користувач не ввійшов у систему, це має ідеальний сенс. Однак якщо користувач уже ввійшов у систему, але не перебуває у необхідній ролі, повертати їх на сторінку входу в оману не можна.

Схоже, це AuthorizeAttributeпоєднує автентифікацію та авторизацію.

Це здається трохи недоглядом у ASP.NET MVC, чи я щось пропускаю?

Мені довелося приготувати DemandRoleAttributeте, що розділяє два. Коли користувач не має автентифікації, він повертає HTTP 401, надсилаючи їх на сторінку входу. Коли користувач увійшов у систему, але не перебуває в необхідній ролі, він створює NotAuthorizedResultнатомість. Наразі це переадресація на сторінку помилок.

Звичайно, мені не довелося цього робити?


10
Відмінне запитання, і я погоджуюсь, це має бути кидання статусу HTTP Not Authorized.
Pure.Krome

3
Мені подобається ваше рішення, Роджере. Навіть якщо ви цього не зробите.
Джон Девіс

На моїй сторінці входу є чек, щоб просто перенаправити користувача на ReturnUrl, якщо він / він вже отримав авторизацію. Так мені вдалося створити нескінченну петлю з 302 переадресацій: D woot.
juhan_h

1
Перевірте це .
Jogi

Роджер, хороша стаття щодо вашого рішення - red-gate.com/simple-talk/dotnet/asp-net/… Здається, ваше рішення - єдиний спосіб зробити це чисто
Крейг

Відповіді:


305

Коли вона була вперше розроблена, System.Web.Mvc.AuthorizeAttribute робив правильно - старіші редакції специфікації HTTP використовували код статусу 401 як для "несанкціонованих", так і для "несанкціонованих".

З оригінальної специфікації:

Якщо запит вже включав в себе облікові дані авторизації, відповідь 401 вказує на те, що авторизація відхилена для цих даних.

Насправді ви можете побачити плутанину саме там - воно використовує слово "авторизація", коли воно означає "автентифікація". У повсякденній практиці, однак, має більш сенс повернути 403 Заборонено, коли користувач має автентифікацію, але не має права. Навряд чи користувач матиме другий набір облікових даних, який дасть їм доступ - поганий досвід користувачів у всьому світі.

Розглянемо більшість операційних систем - при спробі читання файлу у вас немає дозволу на доступ, вам не відображається екран входу!

На щастя, технічні характеристики HTTP були оновлені (червень 2014 року), щоб усунути неоднозначність.

З "Протоколу гіпертекстового транспорту (HTTP / 1.1): автентифікація" (RFC 7235):

Код стану 401 (Несанкціонований) вказує на те, що запит не застосовано, оскільки в ньому відсутні дійсні облікові дані автентифікації для цільового ресурсу.

З "Протоколу передачі гіпертексту (HTTP / 1.1): семантика та вміст" (RFC 7231):

Код стану 403 (Заборонено) вказує на те, що сервер зрозумів запит, але відмовляється його авторизувати.

Цікаво, що під час виходу ASP.NET MVC 1 поведінка AuthorizeAttribute була правильною. Тепер поведінка невірна - специфікація HTTP / 1.1 була виправлена.

Замість того, щоб намагатися змінити переадресацію сторінки входу на ASP.NET, простіше усунути проблему в джерелі. Ви можете створити новий атрибут з тим же ім’ям ( AuthorizeAttribute) у просторі імен за замовчуванням на вашому веб-сайті (це дуже важливо), тоді компілятор автоматично підбере його замість стандартного MVC. Звичайно, ви завжди можете дати атрибуту нове ім'я, якби скоріше скористатися таким підходом.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

52
+1 Дуже хороший підхід. Невелика пропозиція: замість того, щоб перевірити filterContext.HttpContext.User.Identity.IsAuthenticated, ви можете просто перевірити filterContext.HttpContext.Request.IsAuthenticated, до чого вбудовані нульові чеки. Дивіться stackoverflow.com/questions/1379566/…
Daniel Liuzzi

> Ви можете створити новий атрибут з тим самим іменем (AuthorizeAttribute) у просторі імен за замовчуванням на вашому веб-сайті, тоді компілятор автоматично підбере його замість стандартного MVC. Це призводить до помилки: не вдалося знайти тип або простір імен "Авторизувати" (не вистачає директиви чи посилання на збірку?), Використовуючи System.Web.Mvc; і простір імен для мого користувальницького класу AuthorizeAttribute посилаються на контролер. Щоб вирішити це, мені довелося скористатися [MyNamepace.Authorize]
stormwild

2
@DePeter специфікація ніколи нічого не говорить про переспрямування, тому чому переспрямовування є кращим рішенням? Це одне вбиває запити Ajax без злому на місці, щоб вирішити це.
Адам Туліпер - MSFT

1
Це слід увійти в MS Connect, оскільки це явно поведінкова помилка. Дякую.
Tony Wall

BTW, чому нас переспрямовують на сторінку входу? Чому б просто не вивести код 401 та сторінку входу безпосередньо в рамках одного запиту?
SandRock

25

Додайте це до функції входу на сторінку_завантаження:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

Коли користувач перенаправлений туди, але він уже ввійшов у систему, він показує сторінку, що не має права. Якщо вони не ввійшли в систему, він пропускається і показує сторінку входу.


18
Page_Load - це веб-форми mojo
шанс

2
@Chance - тоді зробіть це в ActionMethod за замовчуванням для контролера, який викликається, де було встановлено FormsAuthencation для виклику.
Pure.Krome

Це насправді працює дуже добре, хоча для MVC це має бути щось на зразок, if (User.Identity != null && User.Identity.IsAuthenticated) return RedirectToRoute("Unauthorized");де Несанкціоноване - це визначена назва маршруту.
Мойсей Мачуа

Отже, ви запитуєте ресурс, ви перенаправляєтесь на сторінку входу, і ви знову перенаправляєтесь на сторінку 403? Мені здається погано. Я навіть не переношу одного переадресації взагалі. ІМО ця річ дуже погано побудована.
SandRock

3
Відповідно до вашого рішення, якщо ви вже увійшли до системи та перейдіть на сторінку входу, ввівши URL ... що не вірно.
Райджекар Редді

4

Я завжди думав, що це має сенс. Якщо ви ввійшли в систему і намагаєтеся потрапити на сторінку, яка вимагає ролі, якої у вас немає, ви перейдете на екран входу з проханням увійти з користувачем, який виконує цю роль.

Ви можете додати логіку на сторінку входу, яка перевіряє, чи користувач уже перевірений. Ви можете додати дружнє повідомлення, яке пояснює, чому вони знову стикаються там.


4
Я вважаю, що більшість людей, як правило, не мають більше особистих даних для певного веб-додатка. Якщо вони є, то вони досить розумні, щоб думати, що "у мого поточного посвідчення немає моджо, я ввійду назад як інший".
Роджер Ліпскомб

Хоча ваш інший пункт щодо відображення чогось на сторінці входу є хорошим. Дякую.
Роджер Ліпскомб

4

На жаль, ви маєте справу з поведінкою ASP.NET за типовою поведінкою за замовчуванням. Тут обговорюється вирішення (я цього не пробував):

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(Це не характерно для MVC)

Я думаю, що в більшості випадків найкращим рішенням є обмеження доступу до несанкціонованих ресурсів до того, як користувач намагається потрапити туди. Видаляючи / переглядаючи посилання або кнопку, яка може перенести їх на цю несанкціоновану сторінку.

Напевно, було б добре мати додатковий параметр на атрибут, щоб вказати, куди слід перенаправляти несанкціонованого користувача. Але тим часом я розглядаю AuthorizeAttribute як мережу безпеки.


Я також планую видалити посилання на основі авторизації (тут я десь бачив запитання про це), тому згодом я зашифрую метод розширення HtmlHelper.
Роджер Ліпскомб

1
Мені все ж доводиться перешкоджати користувачеві переходити безпосередньо до URL-адреси, саме про це і є цей атрибут. Я не надто задоволений рішенням користувальницької 401 (здається трохи глобальним), тому спробую моделювати свій NotAuthorizedResult на RedirectToRouteResult ...
Roger Lipscombe

0

Спробуйте це у своєму оброблювачі Application_EndRequest вашого файлу Global.ascx

if (HttpContext.Current.Response.Status.StartsWith("302") && HttpContext.Current.Request.Url.ToString().Contains("/<restricted_path>/"))
{
    HttpContext.Current.Response.ClearContent();
    Response.Redirect("~/AccessDenied.aspx");
}

0

Якщо ви використовуєте aspnetcore 2.0, використовуйте це:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Core
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeApiAttribute : Microsoft.AspNetCore.Authorization.AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.User;

            if (!user.Identity.IsAuthenticated)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }
}

0

У моєму випадку проблема полягала в тому, що "HTTP-специфікація використовувала код статусу 401 як для" несанкціонованих ", так і для" несанкціонованих "". Як сказав ShadowChaser

Це рішення працює для мене:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.