Как мне написать ActionFilter, который гарантирует использование AntiForgeryTokens для каждой операции Post?

Я хочу использовать AntiForgeryTokens для каждого действия HttpPost, используя ActionFilter, который находится в контроллере с именем ControllerBase, от которого наследуется каждый другой контроллер.

Я хочу сделать это, создав ActionFilter, наследуемый от ValidateAntiForgeryToken, который принимает аргумент, указывающий, к каким HTTP-глаголам применять себя. Затем я хочу применить этот фильтр к ControllerBase, чтобы убедиться, что AntiForgeryToken проверяется для КАЖДОЙ операции POST на всем сайте.

Я изучал использование это решение, но

  • AuthorizationContext Constructor (ControllerContext) — это устаревший конструктор, и я не уверен, как перестроить код, используя рекомендуемый AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor).

  • Похоже, что по умолчанию AntiForgeryToken не используется, поскольку я получаю следующую ошибку: A required anti-forgery token was not supplied or was invalid после каждого действия с публикацией.

Как мне переписать мой ActionFilter, чтобы он соответствовал текущим неустаревшим стандартам и правильно использовал маркер защиты от подделки для каждого глагола [HttpPost]?

Должен ли я сам включать токен защиты от подделки в каждую форму (думаю, да)? (в отличие от автоматического создания — не смейтесь, мне любопытно) Обновление: Как указано в комментариях; Да, это нужно делать с каждой формой.

Вот код из моей ControllerBase для справки:

[UseAntiForgeryTokenOnPostByDefault]
public class ControllerBase : Controller 
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class BypassAntiForgeryTokenAttribute : ActionFilterAttribute
    {
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class UseAntiForgeryTokenOnPostByDefault : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (ShouldValidateAntiForgeryTokenManually(filterContext))
            {
                var authorizationContext = new AuthorizationContext(filterContext.Controller.ControllerContext);

                //Use the authorization of the anti forgery token, 
                //which can't be inhereted from because it is sealed
                new ValidateAntiForgeryTokenAttribute().OnAuthorization(authorizationContext);
            }

            base.OnActionExecuting(filterContext);
        }

        /// <summary>
        /// We should validate the anti forgery token manually if the following criteria are met:
        /// 1. The http method must be POST
        /// 2. There is not an existing [ValidateAntiForgeryToken] attribute on the action
        /// 3. There is no [BypassAntiForgeryToken] attribute on the action
        /// </summary>
        private static bool ShouldValidateAntiForgeryTokenManually(ActionExecutingContext filterContext)
        {
            var httpMethod = filterContext.HttpContext.Request.HttpMethod;

            //1. The http method must be POST
            if (httpMethod != "POST") return false;

            // 2. There is not an existing anti forgery token attribute on the action
            var antiForgeryAttributes =
                filterContext.ActionDescriptor.GetCustomAttributes(typeof (ValidateAntiForgeryTokenAttribute), false);

            if (antiForgeryAttributes.Length > 0) return false;

            // 3. There is no [BypassAntiForgeryToken] attribute on the action
            var ignoreAntiForgeryAttributes =
                filterContext.ActionDescriptor.GetCustomAttributes(typeof (BypassAntiForgeryTokenAttribute), false);

            if (ignoreAntiForgeryAttributes.Length > 0) return false;

            return true;
        }
    }
}

person Ecnalyr    schedule 02.08.2012    source источник
comment
Вы можете добавить фильтр в коллекцию Global Filters. Я не уверен, что было бы необходимо поставить его на базовый контроллер.   -  person Dismissile    schedule 02.08.2012
comment
Я понимаю это, но предпочел бы поместить его в ControllerBase для моей текущей архитектуры.   -  person Ecnalyr    schedule 02.08.2012
comment
Должен ли я сам включать маркер защиты от подделки в каждую форму?... Возможно, вы сможете написать собственный BeginForm HtmlHelper, который будет выдавать это автоматически. В противном случае да, вам нужно будет выводить токен на каждую форму вручную.   -  person danludwig    schedule 02.08.2012
comment
@danludwig Спасибо, похоже, это то, что я должен и буду делать.   -  person Ecnalyr    schedule 02.08.2012
comment
@Экналир public static MvcForm BeginSecureForm(this HtmlHelper htmlHelper, RouteValueDictionary routeValues) { var mvcForm = htmlHelper.BeginForm(routeValues); htmlHelper.ViewContext.Writer.Write(htmlHelper.AntiForgeryToken().ToHtmlString()); return mvcForm; }   -  person felickz    schedule 07.11.2013
comment
@felickz этому посту больше года. Спасибо, в любом случае? :)   -  person Ecnalyr    schedule 07.11.2013
comment
@Ecnalyr .. не то чтобы они удаляли посты, пытаясь помочь следующему парню   -  person felickz    schedule 07.11.2013
comment
@felickz Это очень хороший момент. Так держать!   -  person Ecnalyr    schedule 08.11.2013


Ответы (2)


Вам не нужно создавать экземпляры AuthorizationContext или вызывать метод OnAuthorization, просто:

if (ShouldValidateAntiForgeryTokenManually(filterContext))
{
    AntiForgery.Validate(filterContext.HttpContext, null);
}
person Darin Dimitrov    schedule 03.08.2012
comment
Времена меняются: [Устарело (этот метод устарел. Вместо этого используйте метод Validate()., ошибка: правда)] ... теперь он обернут _worker.Validate(new HttpContextWrapper(HttpContext.Current)); - person felickz; 07.11.2013

Я использовал следующий подход:

public class SkipCSRFCheckAttribute : Attribute
{
}

public class AntiForgeryTokenFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (IsHttpPostRequest(filterContext) && !SkipCsrfCheck(filterContext))
            AntiForgery.Validate();
    }

    private static bool IsHttpPostRequest(AuthorizationContext filterContext)
    {
        return filterContext.RequestContext.HttpContext.Request.HttpMethod == HttpMethod.Post.ToString();
    }

    private static bool SkipCsrfCheck(AuthorizationContext filterContext)
    {
        return filterContext.ActionDescriptor.GetCustomAttributes(typeof (SkipCSRFCheck), false).Any();
    }
}

Это позволяет нам отключать его в каждом конкретном случае, используя атрибут SkipCSRFCheck, а затем регистрируя его как глобальный фильтр в Application_Start:

GlobalFilters.Filters.Add(новый AntiForgeryTokenFilter());

person James Crowley    schedule 22.02.2014