I have a very simple scenario. I want to decorate my controllers/actions with a custom authorization attribute. Authorization should be granted if any of the attributes is valid. For example,

[MyAuth(1)]
[MyAuth(2)]
public class MyController : Controller
{
    ...
}

I cannot combine the parameters into a single authorization attribute. The above example is a simplified example, only.

If either attribute authorizes the user, I want the user to be authorized. I assumed that ActionFilterAttribute or AuthorizeAttribute would have the means to see what other filters have been executed and are waiting to be executed, but no such luck.

How can I accomplish this? Since the attributes don't seem to have any awareness, maybe an HttpModule? A custom ControllerActionInvoker?

有帮助吗?

解决方案

I managed to get this to work last night. My solution is below. The attribute is pretty standard and I've trimmed the actual authorization parts. The interesting stuff happens in HasAssignedAcccessActionInvoker.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class RequiresAssignedAccess : AuthorizeAttribute
{
    public int AccessType { get; private set; }
    public int IdType { get; private set; }
    public int IdValue { get; private set; }
    public int Level { get; private set; }

    public RequiresAssignedAccess(int accessType, int idType, int idValue, int level)
    {
        ...
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (!base.AuthorizeCore(httpContext))
            return false;

        bool retval = ...

        return retval;
    }
}

HasAssignedAcccessActionInvoker inherits from the standard action invoker, but I overrode the InvokeAuthorizationFilters method to add the authorization logic we need. The standard invoker just spins through the authorization filters and if any of them returns a result, it breaks the loop.

public class HasAssignedAcccessActionInvoker : ControllerActionInvoker
{
    protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
    {
        AuthorizationContext authCtx = new AuthorizationContext(controllerContext, actionDescriptor);

        /*
         * If any of the filters are RequiresAssignedAccess, default this to false.  One of them must authorize the user.
         */
        bool hasAccess = !filters.Any(f => f is RequiresAssignedAccess);

        foreach (IAuthorizationFilter current in filters)
        {
            /*
             * This sets authorizationContext.Result, usually to an instance of HttpUnauthorizedResult
             */
            current.OnAuthorization(authCtx);

            if (current is RequiresAssignedAccess)
            {
                if (authCtx.Result == null)
                {
                    hasAccess = true;
                }
                else if (authCtx.Result is HttpUnauthorizedResult)
                {
                    authCtx.Result = null;
                }

                continue;
            }

            if (authCtx.Result != null)
                break;
        }

        if (!hasAccess && authCtx.Result == null)
            authCtx.Result = new HttpUnauthorizedResult();

        return authCtx;
    }
}

I had to look at MVC's internals with ILSpy to figure this out. For reference, this is the overridden version of that method:

protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
{
    AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor);
    foreach (IAuthorizationFilter current in filters)
    {
        current.OnAuthorization(authorizationContext);
        if (authorizationContext.Result != null)
        {
            break;
        }
    }
    return authorizationContext;
}

Lastly, to wire this up and make everything possible, our controllers inherit from BaseController, which now returns the new invoker.

public class BaseController : Controller
{
    protected override IActionInvoker CreateActionInvoker()
    {
        return new HasAssignedAcccessActionInvoker();
    }
}

其他提示

As far as I know, you cannot chain [Authorize] attributes in the manner that you want because they all have to pass (AND) and not (OR) behavior. However, the combining of the items into one does not cause you to have to do some magic string manipulation, regardless of the number of parameters that you need to pass to it. You can define your own set of parameters that are available to the Authorize attribute.

public class SuperCoolAuthorize : AuthorizationAttribute
{
    public string Parameter1{get;set;}
    public string Parameter2{get;set;}
    public int Parameter3{get;set;}
    public string Parameter4{get;set;}
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // your custom behaviour
    }
}

And on your controller/action method

[Authorize(Parameter1 = "Foo", Parameter2 = "Bar", Parameter3 = 47, Parameter4 = string.Empty)
public ActionResult MyControllerAction(){
...
}

A great post on some other considerations on custom Authorizing attributes I came across in helping to formulate this answer.

public class AuthUserAttribute : AuthorizeAttribute {

public string[] SecurityGroups;
public string Groups { get; set; }

protected override bool AuthorizeCore(HttpContextBase httpContext) {
  bool valid = false;

  var user = UserInformation.Current;

  if (user.SecurityGroups.Select(x => x).Intersect(this.SecurityGroups).Any()) {
    valid = true;
  }

  if (user.SecurityGroups.Select(x => x).Intersect(new string[] { "IT Administrators" }).Any()) {
    valid = true;
  }

  return valid;
}

public override void OnAuthorization(AuthorizationContext filterContext) {
  if (!this.AuthorizeCore(filterContext.HttpContext)) {
    if (UserInformation.Current.SecurityGroups.Count == 0) {
      filterContext.Result = new RedirectResult(string.Format("/oa?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl));
    }
    else {
      filterContext.Result = new RedirectResult(string.Format("/oa/user/permissions?ReturnUrl={0}", filterContext.HttpContext.Request.RawUrl));
    }
  }
  else {
    base.OnAuthorization(filterContext);
  }
}

}

then I decorate with

[AuthUser(SecurityGroups = new string[] { "Data1", "Data2" })]
public ActionResult ForYourEyesOnly() {


}

We'll see if anyone catches the Bond reference. LOL

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top