【问题标题】:Passing Func as an attribute parameter to secure MVC routes将 Func 作为属性参数传递以保护 MVC 路由
【发布时间】:2010-07-28 21:17:41
【问题描述】:

我正在尝试保护我的 MVC 路由免受一组满足一组条件的用户的影响。由于 MVC 似乎使用了相当多的属性,而 Steven Sanderson 在他的专业 MVC 书籍中使用了一个属性来实现安全可扩展性,我开始沿着这条路线走下去,但我想根据我应用它的操作来根据上下文定义规则。

有些操作仅适用于员工,有些则不是。

有些操作仅适用于 company1,有些则不是。

所以我在想这种用法……

[DisableAccess(BlockUsersWhere = u => u.Company != "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}

[DisableAccess(BlockUsersWhere = u => u.IsEmployee == false)]
public ActionResult EmployeeOnlyAction()
{
...
}

对我来说看起来很干净并且非常容易实现,但是我收到以下编译器错误:

'BlockUsersWhere' 不是有效的命名属性参数,因为它不是有效的属性参数类型

显然你不能使用 Func 作为属性参数。有什么其他建议可以解决这个问题或提供我们在 MVC 项目中喜欢的简单用法的其他建议吗?

【问题讨论】:

    标签: c# asp.net asp.net-mvc attributes


    【解决方案1】:

    Necros 的建议可行,但您必须在每个操作方法的主体中调用他的 SecurityGuard 助手。

    如果您仍想使用声明性基于属性的方法(其优点是您可以将属性应用于整个控制器),您可以编写自己的 AuthorizeAttribute

    public class CustomAuthorizeAttribute : AuthorizeAttribute {
        public bool EmployeeOnly { get; set; }
        private string _company;
    
        public string Company {
            get { return _company; }
            set { _company = value; }
        }
    
    
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
            return base.AuthorizeCore(httpContext) && MyAuthorizationCheck(httpContext);
        }
    
        private bool MyAuthorizationCheck(HttpContextBase httpContext) {
            IPrincipal user = httpContext.User;
    
            if (EmployeeOnly && !VerifyUserIsEmployee(user)) {
                return false;
            }
    
            if (!String.IsNullOrEmpty(Company) && !VerifyUserIsInCompany(user)) {
                return false;
            }
    
            return true;
        }
    
        private bool VerifyUserIsInCompany(IPrincipal user) {
            // your check here
        }
    
        private bool VerifyUserIsEmployee(IPrincipal user) {
            // your check here
        }
    }
    

    那么你会如下使用它

    [CustomAuthorize(Company = "Acme")]   
    public ActionResult AcmeOnlyAction()   
    {   
    ...   
    }   
    
    [CustomAuthorize(EmployeeOnly = true)]   
    public ActionResult EmployeeOnlyAction()   
    {   
    ...   
    }  
    

    【讨论】:

    • 谢谢,这是我想到的第一件事,但是使用这种方法,我失去了在上下文基础上创建独特规则的能力,而不必更新属性类。我可能会沿着这条路走。
    • 是的,属性只能传达有限的信息。他们真的应该只是关于分配元数据。更复杂的行为属于代码。
    【解决方案2】:

    由于您只能在属性参数中使用常量、类型或数组初始值设定项,它们可能不会这样做,或者至少不会那么灵活。

    或者,您可以使用我在解决此问题时提出的类似方法。

    这是 API:

    public static class SecurityGuard
    {
        private const string ExceptionText = "Permission denied.";
    
        public static bool Require(Action<ISecurityExpression> action)
        {
            var expression = new SecurityExpressionBuilder();
            action.Invoke(expression);
            return expression.Eval();
        }
    
        public static bool RequireOne(Action<ISecurityExpression> action)
        {
            var expression = new SecurityExpressionBuilder();
            action.Invoke(expression);
            return expression.EvalAny();
        }
    
        public static void ExcpetionIf(Action<ISecurityExpression> action)
        {
            var expression = new SecurityExpressionBuilder();
            action.Invoke(expression);
            if(expression.Eval())
            {
                throw new SecurityException(ExceptionText);
            }
        }
    }
    
    public interface ISecurityExpression
    {
        ISecurityExpression UserWorksForCompany(string company);
        ISecurityExpression IsTrue(bool expression);
    }
    

    然后创建一个表达式构建器:

    public class SecurityExpressionBuilder : ISecurityExpression
    {
        private readonly List<SecurityExpression> _expressions;
    
        public SecurityExpressionBuilder()
        {
            _expressions = new List<SecurityExpression>();
        }
    
        public ISecurityExpression UserWorksForCompany(string company)
        {
            var expression = new CompanySecurityExpression(company);
            _expressions.Add(expression);
            return this;
        }
    
        public ISecurityExpression IsTrue(bool expr)
        {
            var expression = new BooleanSecurityExpression(expr);
            _expressions.Add(expression);
            return this;
        }
    
        public bool Eval()
        {
            return _expressions.All(e => e.Eval());
        }
    
        public bool EvalAny()
        {
            return _expressions.Any(e => e.Eval());
        }
    }
    

    实现安全表达式:

    internal abstract class SecurityExpression
    {
        public abstract bool Eval();
    }
    
    internal class BooleanSecurityExpression : SecurityExpression
    {
        private readonly bool _result;
    
        public BooleanSecurityExpression(bool expression)
        {
            _result = expression;
        }
    
        public override bool Eval()
        {
            return _result;
        }
    }
    
    internal class CompanySecurityExpression : SecurityExpression
    {
        private readonly string _company;
    
        public CompanySecurityExpression(string company)
        {
            _company = company;
        }
    
        public override bool Eval()
        {
            return (WhereverYouGetUser).Company == company;
        }
    }
    

    您可以根据需要添加任意数量的自定义表达式。基础设施有点复杂,但是使用起来真的很简单:

    public ActionResult AcmeOnlyAction()
    {
        SecurityGuard.ExceptionIf(s => s.UserWorksForCompany("Acme"));
    }
    

    您还可以链接表达式,并将其用作示例中的条件(使用SecurityGuard.Require())。

    抱歉,长帖,希望对你有帮助。

    【讨论】:

      猜你喜欢
      • 2017-07-31
      • 2017-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-22
      • 1970-01-01
      相关资源
      最近更新 更多