make sure each controller method has a ValidateAnt

2019-01-31 14:45发布

问题:

Is there any way to centralize enforcement that every action method must have a "ValidateAntiForgeryToken" attribute? I'm thinking it would have to be done by extending one the "routing" classes.

Edit: Or maybe do some reflection at application startup?

回答1:

Yes. You can do this by creating your own BaseController that inherits the Mvc Controller, and overloads the OnAuthorization(). You want to make sure it is a POST event before enforcing it:

public abstract class MyBaseController : Controller
{
  protected override void OnAuthorization(AuthorizationContext filterContext)
  {
    //enforce anti-forgery stuff for HttpVerbs.Post
    if (String.Compare(filterContext.HttpContext.Request.HttpMethod,
          System.Net.WebRequestMethods.Http.Post, true) == 0)
    {
      var forgery = new ValidateAntiForgeryTokenAttribute();
      forgery.OnAuthorization(filterContext);
    }
    base.OnAuthorization(filterContext);
  }
}

Once you have that, make sure all of your controllers inherit from this MyBaseController (or whatever you call it). Or you can do it on each Controller if you like with the same code.



回答2:

Sounds like you're trying to prevent "oops I forgot to set that" bugs. If so I think the best place to do this is with a custom ControllerActionInvoker.

Essentially what you want to do is stop MVC from even finding an action without a AntiForgery token:

public class MustHaveAntiForgeryActionInvoker : ControllerActionInvoker
{
    protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
    {
        var foundAction = base.FindAction(controllerContext, controllerDescriptor, actionName);

        if( foundAction.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true ).Length == 0 )
            throw new InvalidOperationException("Can't find a secure action method to execute");

        return foundAction;
    }
}

Then in your controller, preferably your base controller:

ActionInvoker = new MustHaveAntiForgeryActionInvoker();

Just wanted to add that custom Controller base classes tend to get "thick" and imo its always best practice to use MVC's brilliant extensibility points to hook in the features you need where they belong.

Here is a good guide of most of MVC's extensibility points: http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx



回答3:

Ok, I just upgraded a project to MVC v2.0 here, and eduncan911's solution doesn't work anymore if you use the AuthorizeAttribute on your controller actions. It was somewhat hard to figure out why.

So, the culprit in the story is that the MVC team added the use of the ViewContext.HttpContext.User.Identity.Name property in the value for the RequestVerificationToken.

The overridden OnAuthorization in the base controller is executed before any filters on the controller action. So, the problem is that the Authorize attribute has not yet been invoked and therefore is the ViewContext.HttpContext.User not set. So the UserName is String.Empty whereas the AntiForgeryToken used for validation includes the real user name = fail.

We solved it now with this code:

public abstract class MyBaseController : Controller
{
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        //enforce anti-forgery stuff for HttpVerbs.Post
        if (String.Compare(filterContext.HttpContext.Request.HttpMethod, "post", true) == 0)
        {
            var authorize = new AuthorizeAttribute();
            authorize.OnAuthorization(filterContext);
            if (filterContext.Result != null) // Short circuit validation
                return;
            var forgery = new ValidateAntiForgeryTokenAttribute();
            forgery.OnAuthorization(filterContext);
        }
        base.OnAuthorization(filterContext);
    }
}

Some references to the MVC code base:

ControllerActionInvoker#InvokeAuthorizationFilters() line 283. Same short circuiting. AntiForgeryData#GetUsername() line 98. New functionality.



回答4:

How about this?

[ValidateAntiForgeryToken]
public class MyBaseController : Controller
{
}