MVC 5.0 [AllowAnonymous] и новый IAuthenticationFilter
Когда я создаю новое приложение asp.net mvc 4.0, одним из первой вещи, которую я делаю, является создание и установка настраиваемого авторизации global filter
следующим образом:
//FilterConfig.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new CustomAuthorizationAttribute());
}
Затем я создаю CustomAuthorizationAttribute
следующим образом:
//CustomAuthorizationAttribute.cs
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
//Handle AJAX requests
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
{
//Handle regular requests
base.HandleUnauthorizedRequest(filterContext); //let FormsAuthentication make the redirect based on the loginUrl defined in the web.config (if any)
}
}
У меня есть два контроллера: HomeController
и SecureController
HomeController украшен атрибутом [AllowAnonymous]
.
SecureController НЕ, украшенный атрибутом [AllowAnonymous]
.
Index() ActionResult
HomeController
отображает вид с помощью простой кнопки.
Когда я нажимаю кнопку, я делаю вызов ajax методу GetData(), который живет внутри SecureController
, например:
$("#btnButton").click(function () {
$.ajax({
url: '@Url.Action("GetData", "Secure")',
type: 'get',
data: {param: "test"},
success: function (data, textStatus, xhr) {
console.log("SUCCESS GET");
}
});
});
Излишне говорить, что когда я нажимаю кнопку, я запускаю CustomAuthorizationAttribute
, потому что это глобальный фильтр, но также потому, что SecureController
НЕ украшен атрибутом [AllowAnonymous]
.
Хорошо, я сделал с моим вступлением...
С введением asp.net mvc 5.0
мы теперь вводим новый authentication filter
, который вызывает срабатывание до фильтра авторизации (что отлично и дает нам более подробный контроль над тем, как я может отличаться от пользователя, который НЕ аутентифицирован (http 401) от пользователя, который прошел аутентификацию IS и кто НЕ НЕ разрешен (http 403)).
Чтобы дать этой новой попытке authentication filter
, Ive создал новый asp.net mvc 5.0 (VS Express 2013 для Интернета) и начал с выполнения следующего действия:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new CustomAuthenticationAttribute()); //Notice I'm using the word Authentication and not Authorization
}
Затем атрибут
public class CustomAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
Ive создал a HomeController
. HomeController
украшен атрибутом [AllowAnonymous]
.
Перед запуском приложения из VS 2013 Ive установил две точки останова в обоих методах моего атрибута CustomAuthenticationAttribute (OnAuthentication
и OnAuthenticationChallenge
).
Когда я запускаю приложение, я ударяю первую точку прерывания (OnAuthentication
). Затем, к моему удивлению, код в Index() ActionResult
моего HomeController
выполняется и только после того, как я возвращаю View(), я попал в точку прерывания по методу OnAuthenticationChallenge()
.
Вопросы:
У меня два вопроса.
Вопрос 1)
У меня создалось впечатление, что атрибут [AllowAnonymous]
обходит любой код внутри моего CustomAuthenticationAttribute
, но я ошибся! Мне нужно вручную проверить на наличие атрибута [AllowAnonymous]
и пропустить любой код?
Вопрос 2)
Почему код внутри моего метода Index()
моего HomeController
выполняется после OnAuthentication
? Только для того, чтобы понять, что после того, как я возвращаю View(), выполняется ли код внутри OnAuthenticationChallenge()
?
Меня беспокоит, что я не хочу, чтобы код из метода Index()
выполнялся, если пользователь НЕ аутентифицирован.
Возможно, я смотрю на это неправильно.
Если кто-то может помочь мне пролить свет на это, это будет здорово!
С уважением
Vince
Ответы
Ответ 1
Что касается:
Вопрос 1)
У меня создалось впечатление, что атрибут [AllowAnonymous] автоматически пропустит любой код в моем атрибуте CustomAuthenticationAttribute, но я ошибся! Нужно ли вручную проверять наличие атрибута [AllowAnonymous] и пропустить любой код?
Насколько я знаю, атрибут [AllowAnonymous] не имеет ничего общего с атрибутом CustomAuthenticationAttribute. У них разные цели. [AllowAnonymous] будет иметь эффект во время контекста авторизации, но не в контексте аутентификации.
Для настройки контекста аутентификации был реализован фильтр проверки подлинности. Например,
AuthenticationContext предоставляет вам информацию для выполнения проверки подлинности. Вы можете использовать эту информацию для принятия решений по аутентификации на основе текущего контекста. Например, вы можете решить изменить ActionResult на другой тип результата на основе контекста проверки подлинности или вы можете изменить текущий директор на основе контекста проверки подлинности и т.д.
Метод OnAuthenticationChallenge запускается после метода OnAuthentication. Вы можете использовать метод OnAuthenticationChallenge для выполнения дополнительных задач в запросе.
Что касается:
Вопрос 2) Почему код внутри моего метода Index() моего HomeController выполняется после проверки OnAuthentication? Только чтобы понять, что после того, как я возвращаю View(), выполняется ли код внутри OnAuthenticationChallenge()?
Это ожидаемое поведение. Поскольку у вас есть глобально зарегистрированный фильтр проверки подлинности, самое первое, что до того, как какое-либо действие будет выполнено, оно должно сначала запустить событие OnAuthentication, как вы бы заметили. Затем выполняется OnAuthenticationChallenge после выполнения Индекса. После того, как действие выполнено успешно, любой фильтр проверки подлинности, соответствующий действию (например, индексу), будет запускать OnAuthenticationChallenge, чтобы он мог способствовать результату действия. Как и в вашем коде для OnAuthenticationChallenge, вы можете изменить ActionResult на HttpUnauthorizedResult, и это будет согласовано с ActionResult.
Ответ 2
Мне нужно дать пояснение к вашему второму вопросу:
Вопрос 2) Почему код внутри моего метода Index() моего HomeController запускается после OnAuthentication? Только для поймите, что после того, как я верну View(), сделайте код внутри Выполняется выполнение функции OnAuthenticationChallenge()?
Фактически вы должны тестировать учетные данные в OnAuthentication, если вы хотите, чтобы пользователь не выполнял код в вашем методе действий. OnAuthenticationChallenge - это ваш шанс обработать 401 с помощью настраиваемого результата, например перенаправить пользователя на пользовательский контроллер/действие и дать им возможность аутентифицироваться.
public class CustomAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
// modify filterContext.Result to go somewhere special...if you do
// nothing here they will just go to the site default login
}
}
Вот более полный прогон фильтра и как вы можете с ним работать: http://jameschambers.com/2013/11/working-with-iauthenticationfilter-in-the-mvc-5-framework/
Приветствия.
Ответ 3
В ответ на вопрос 1:
Атрибут [AllowAnnoymous] действует как флаг (в нем фактически нет логики реализации). Его присутствие проверяется только атрибутом [Авторизовать] во время выполнения OnAuthorization. Декомпиляция атрибута [Авторизовать] показывает логику:
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
[AllowAnnonymous] никогда не будет "автоматически" обходить код в вашем настраиваемом атрибуте...
Таким образом, ответ на вторую половину Вопроса 1: Да - если вы хотите, чтобы ваш пользовательский атрибут реагировал на присутствие [AllowAnnonymous], вам нужно будет выполнить проверку (аналогичную выше) для [AllowAnnonymous] в вашем пользовательском атрибуте [Авторизовать].