多个彼此了解的授权属性

时间:2022-09-08 11:28:03

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.

如果任一属性授权用户,我希望用户被授权。我假设ActionFilterAttribute或AuthorizeAttribute可以看到其他过滤器已被执行并等待执行,但没有这样的运气。

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

我怎么能做到这一点?由于属性似乎没有任何意识,可能是一个HttpModule?自定义ControllerActionInvoker?

3 个解决方案

#1


6  

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.

昨晚我设法让这个工作。我的解决方案如下。该属性非常标准,我已经修剪了实际的授权部分。有趣的东西发生在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.

HasAssignedAcccessActionInvoker继承自标准动作调用程序,但我重写了InvokeAuthorizationFilters方法以添加我们需要的授权逻辑。标准调用者只是通过授权过滤器旋转,如果它们中的任何一个返回结果,它会打破循环。

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:

我不得不看看MVC与ILSpy的内部结构来解决这个问题。作为参考,这是该方法的重写版本:

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.

最后,为了使这一切成为可能,我们的控制器继承自BaseController,它现在返回新的调用者。

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

#2


0  

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.

据我所知,你不能以你想要的方式链接[授权]属性,因为它们都必须传递(AND)而不是(OR)行为。但是,将项目合并为一个不会导致您必须执行一些魔术字符串操作,无论您需要传递给它的参数数量。您可以定义自己的Authorize属性可用的参数集。

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.

关于自定义授权属性的其他一些注意事项的一篇很棒的帖子我在帮助制定这个答案时遇到了。

#3


0  

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

我们会看到是否有人抓住了Bond参考。大声笑

#1


6  

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.

昨晚我设法让这个工作。我的解决方案如下。该属性非常标准,我已经修剪了实际的授权部分。有趣的东西发生在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.

HasAssignedAcccessActionInvoker继承自标准动作调用程序,但我重写了InvokeAuthorizationFilters方法以添加我们需要的授权逻辑。标准调用者只是通过授权过滤器旋转,如果它们中的任何一个返回结果,它会打破循环。

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:

我不得不看看MVC与ILSpy的内部结构来解决这个问题。作为参考,这是该方法的重写版本:

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.

最后,为了使这一切成为可能,我们的控制器继承自BaseController,它现在返回新的调用者。

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

#2


0  

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.

据我所知,你不能以你想要的方式链接[授权]属性,因为它们都必须传递(AND)而不是(OR)行为。但是,将项目合并为一个不会导致您必须执行一些魔术字符串操作,无论您需要传递给它的参数数量。您可以定义自己的Authorize属性可用的参数集。

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.

关于自定义授权属性的其他一些注意事项的一篇很棒的帖子我在帮助制定这个答案时遇到了。

#3


0  

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

我们会看到是否有人抓住了Bond参考。大声笑