AllowAnonymous не работает с пользовательским атрибутом авторизации

Это поставило меня в тупик на некоторое время. Похоже, что ни одна из часто встречающихся подобных ситуаций здесь не применима. Я, наверное, пропустил что-то очевидное, но не вижу этого.

В моем веб-приложении Mvc я использую атрибуты Authorize и AllowAnonymous таким образом, что вам нужно явно открыть действие как общедоступное, а не заблокировать защищенные области сайта. Я предпочитаю такой подход. Однако я не могу получить такое же поведение в моем WebAPI.

Я написал пользовательский атрибут авторизации, который наследуется от System.Web.Http.AuthorizeAttribute со следующим:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class MyAuthorizationAttribute : System.Web.Http.AuthorizeAttribute

Я зарегистрировал это как фильтр:

    public static void RegisterHttpFilters(HttpFilterCollection filters)
    {
        filters.Add(new MyAuthorizationAttribute());
    }

Все это работает как положено, действия больше не доступны без учетных данных. Проблема в том, что теперь следующий метод не позволит атрибуту AllowAnonymous делать свое дело:

[System.Web.Http.AllowAnonymous]
public class HomeController : ApiController
{
    [GET("/"), System.Web.Http.HttpGet]
    public Link[] Index()
    {
        return new Link[] 
        { 
            new SelfLink(Request.RequestUri.AbsoluteUri, "api-root"),
            new Link(LinkRelConstants.AuthorizationEndpoint, "OAuth/Authorize/", "authenticate"),
            new Link(LinkRelConstants.AuthorizationTokenEndpoint , "OAuth/Tokens/", "auth-token-endpoint")
        };
    }
}

Наиболее распространенным сценарием является смешивание двух атрибутов Authorize / AllowAnonymous. System.Web.Mvc - для веб-приложений, а System.Web.Http - для WebAPI (как я понимаю, в любом случае).

Оба атрибута, которые я использую, находятся в одном и том же пространстве имен - System.Web.Http. Я предполагал, что это просто унаследует базовую функциональность и позволит мне внедрить нужный мне код в метод OnAuthotize.

Согласно документации, атрибут AllowAnonymous работает внутри метода OnAuthorize, который я немедленно вызываю:

    public override void OnAuthorization(HttpActionContext actionContext)
    {
        base.OnAuthorization(actionContext);

Любая мысль будет очень ценится.

Кто-нибудь сталкивался с этой проблемой раньше и нашел основную причину?

6 ответов

Решение

В AuthorizeAttribute есть следующий код:

private static bool SkipAuthorization(HttpActionContext actionContext)
{
    Contract.Assert(actionContext != null);

    return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
               || actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}

Включите этот метод в свой класс AuthorizeAttribute, а затем добавьте следующее в начало метода OnAuthorization, чтобы пропустить авторизацию, если обнаружены какие-либо атрибуты AllowAnonymous:

if (SkipAuthorization(actionContext)) return;

ASP.NET MVC 4:

bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
                         || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);

или же

 private static bool SkipAuthorization(AuthorizationContext filterContext)
    {
        Contract.Assert(filterContext != null);

        return filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any()
               || filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any();
    }

Soruce: http://weblogs.asp.net/jongalloway/asp-net-mvc-authentication-global-authentication-and-allow-anonymous

В моем случае ни одно из вышеперечисленных решений не помогло. Я использую.Net Core 3.1 с настраиваемым IAuthorizationFilter и мне пришлось сделать следующее:

public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any()) return;

Использование MVC 5
Шаги, чтобы преодолеть эту проблему: -
1. Обновите свой анонимный атрибут проекта WebAPI и сделайте его похожим

[System.Web.Mvc.AllowAnonymous]
  1. Теперь перейдите в свой класс атрибутов и напишите код

     public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new UnauthorizedAccessException("Access Token Required");
        }
        base.OnAuthorization(filterContext);
        if (filterContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
        {
            return;
        }
        if (filterContext.Request.Headers.Authorization != null)
        {
            var response = 
     PTPRestClient.GetRequest(filterContext.Request.Headers.Authorization.ToString(), 
     "api/validate/validate-request");
            if (!response.IsSuccessStatusCode)
            {
                throw new UnauthorizedAccessException();
            }
    
    
        }
        else
        {
            throw new UnauthorizedAccessException("Access Token Required");
        }
    }
    

Использование C#6.0 Создайте статический класс, который расширяет ActionExecutingContext.

public static class AuthorizationContextExtensions {
    public static bool SkipAuthorization(this ActionExecutingContext filterContext) {    
         Contract.Assert(filterContext != null);
         return filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any()|| filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any();
    }
}

Теперь ваш переопределенный filterContext сможет вызывать метод расширения, просто убедитесь, что они находятся в одном и том же пространстве имен, или включите правильный оператор using.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustomAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        if (filterContext.SkipAuthorization()) return;// CALL EXTENSION METHOD
         /*NOW DO YOUR LOGIC FOR NON ANON ACCESS*/
    }
}

Вот решение для ASP.NET Core 2+ и ASP.NET Core 3+. Добавьте его в реализацию IAsyncAuthorizationFilter:

private static bool HasAllowAnonymous(AuthorizationFilterContext context)
{
    var filters = context.Filters;
    return filters.OfType<IAllowAnonymousFilter>().Any();
}

И проверяем вот так:

public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
    if(HasAllowAnonymous(context))
        return;
}

Я должен использовать другую версию.net Framework или веб-API, но, надеюсь, это кому-то поможет:

        bool skipAuthorization = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any() || actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
        if (skipAuthorization)
        {
            return;
        }
public class MyAuthorizationAuthorize : AuthorizeAttribute, IAuthorizationFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAuthenticated)
            {
                bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
                    filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);

                if (skipAuthorization) return;

            }
            else filterContext.Result = new HttpUnauthorizedResult();
        }
}
Другие вопросы по тегам