【问题标题】:Stuck creating a "security trimmed" html.ActionLink extension method卡住创建“安全修剪”html.ActionLink 扩展方法
【发布时间】:2008-09-23 13:30:41
【问题描述】:

我正在尝试为 MVC 的 htmlHelper 创建一个扩展方法。 目的是根据控制器/动作上设置的 AuthorizeAttribute 启用或禁用 ActionLink。 借用MVCSitemap
Maarten Balliauw 创建的代码,我想在决定如何呈现操作链接之前验证用户对控制器/操作的权限。 当我尝试获取 MvcHandler 时,我得到一个空值。 控制器/动作的属性是否有更好的方法?

扩展方法的代码如下:

public static class HtmlHelperExtensions
{
    public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller)
    {
        //simplified for brevity 
        if (IsAccessibleToUser(action, controller))
        {
            return htmlHelper.ActionLink(linkText, action,controller);    
        }
        else
        {
            return String.Format("<span>{0}</span>",linkText);    
        }
    }

    public static bool IsAccessibleToUser(string action, string controller)
    {
        HttpContext context = HttpContext.Current;

        MvcHandler handler = context.Handler as MvcHandler;            

        IController verifyController = 
            ControllerBuilder
            .Current
            .GetControllerFactory()
            .CreateController(handler.RequestContext, controller);

        object[] controllerAttributes = verifyController.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
        object[] actionAttributes = verifyController.GetType().GetMethod(action).GetCustomAttributes(typeof(AuthorizeAttribute), true);

        if (controllerAttributes.Length == 0 && actionAttributes.Length == 0)
            return true;

        IPrincipal principal = handler.RequestContext.HttpContext.User;

        string roles = "";
        string users = "";
        if (controllerAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }
        if (actionAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }

        if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
            return true;

        string[] roleArray = roles.Split(',');
        string[] usersArray = users.Split(',');
        foreach (string role in roleArray)
        {
            if (role != "*" && !principal.IsInRole(role)) return false;
        }
        foreach (string user in usersArray)
        {
            if (user != "*" && (principal.Identity.Name == "" || principal.Identity.Name != user)) return false;
        }
        return true;
    }

}

【问题讨论】:

    标签: asp.net-mvc


    【解决方案1】:

    这是工作代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Security.Principal;
    using System.Web.Routing;
    using System.Web.Mvc;
    using System.Collections;
    using System.Reflection;
    namespace System.Web.Mvc.Html
    {
        public static class HtmlHelperExtensions
        {
            public static string SecurityTrimmedActionLink(
            this HtmlHelper htmlHelper,
            string linkText,
            string action,
            string controller)
            {
                return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, false);
            }
            public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled)
            {
                if (IsAccessibleToUser(action, controller))
                {
                    return htmlHelper.ActionLink(linkText, action, controller);
                }
                else
                {
                    return showDisabled ? String.Format("<span>{0}</span>", linkText) : "";
                }
            }
            public static bool IsAccessibleToUser(string actionAuthorize, string controllerAuthorize)
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                GetControllerType(controllerAuthorize);
                Type controllerType = GetControllerType(controllerAuthorize);
                var controller = (IController)Activator.CreateInstance(controllerType);
                ArrayList controllerAttributes = new ArrayList(controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true));
                ArrayList actionAttributes = new ArrayList();
                MethodInfo[] methods = controller.GetType().GetMethods();
                foreach (MethodInfo method in methods)
                {
                    object[] attributes = method.GetCustomAttributes(typeof(ActionNameAttribute), true);
                    if ((attributes.Length == 0 && method.Name == actionAuthorize) || (attributes.Length > 0 && ((ActionNameAttribute)attributes[0]).Name == actionAuthorize))
                    {
                        actionAttributes.AddRange(method.GetCustomAttributes(typeof(AuthorizeAttribute), true));
                    }
                }
                if (controllerAttributes.Count == 0 && actionAttributes.Count == 0)
                    return true;
    
                IPrincipal principal = HttpContext.Current.User;
                string roles = "";
                string users = "";
                if (controllerAttributes.Count > 0)
                {
                    AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
                    roles += attribute.Roles;
                    users += attribute.Users;
                }
                if (actionAttributes.Count > 0)
                {
                    AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
                    roles += attribute.Roles;
                    users += attribute.Users;
                }
    
                if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
                    return true;
    
                string[] roleArray = roles.Split(',');
                string[] usersArray = users.Split(',');
                foreach (string role in roleArray)
                {
                    if (role == "*" || principal.IsInRole(role))
                        return true;
                }
                foreach (string user in usersArray)
                {
                    if (user == "*" && (principal.Identity.Name == user))
                        return true;
                }
                return false;
            }
    
            public static Type GetControllerType(string controllerName)
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
                    {
                        return type;
                    }
                }
                return null;
            }
        }
    }
    

    我不喜欢使用反射,但我无法访问 ControllerTypeCache。

    【讨论】:

    • 链接已损坏。您能否将其更新到新位置(如果存在)?
    • 如上,链接已损坏。请更新。我很想给-1,但现在不会。虽然我建议阅读meta.stackoverflow.com/help/how-to-answer 的“为链接提供上下文”部分。即,如果目标站点不可用,您应该始终在答案中包含更多详细信息。
    【解决方案2】:

    您的 ViewPage 具有对视图上下文的引用,因此您可以将其作为扩展方法。

    那你可以说是 Request.IsAuthenticated 还是 Request.User.IsInRole(...)

    用法类似于&lt;%= this.SecurityLink(text, demandRole, controller, action, values) %&gt;

    【讨论】:

    • 我正在尝试从 AuthorizeAttribute 获取角色以将它们与用户角色进行比较。我不确定这是怎么做到的。
    • 重点是,一旦在 AuthorizeAttribute 中指定了角色,您就不再需要将它们添加到每个单独的链接中。
    【解决方案3】:

    我真的很喜欢 @Robert 帖子中的代码,但是有一些错误,我想缓存角色和用户的收集,因为反射可能会花费一些时间。

    修复的错误:如果同时存在 Controller 属性和 Action 属性,则当角色连接时,不会在控制器角色和操作角色之间插入额外的逗号,从而无法正确分析。

    [Authorize(Roles = "SuperAdmin,Executives")]
    public class SomeController() {
        [Authorize(Roles = "Accounting")]    
        public ActionResult Stuff() {
        }
    }
    

    那么角色字符串最终是SuperAdmin,ExecutivesAccounting,我的版本确保执行人员和会计是分开的。

    我的新代码也忽略了 HttpPost 操作上的 Auth,因为这可能会导致事情失败,尽管不太可能。

    最后,对于较新版本的 MVC,它返回 MvcHtmlString 而不是 string

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Reflection;
    using System.Collections;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
    using System.Security.Principal;
    
    
    public static class HtmlHelperExtensions
    {
        /// <summary>
        /// only show links the user has access to
        /// </summary>
        /// <returns></returns>
        public static MvcHtmlString SecurityLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled = false)
        {
            if (IsAccessibleToUser(action, controller))
            {
                return htmlHelper.ActionLink(linkText, action, controller);
            }
            else
            {
                return new MvcHtmlString(showDisabled ? String.Format("<span>{0}</span>", linkText) : "");
            }
        }
    
        /// <summary>
        /// reflection can be kinda slow, lets cache auth info
        /// </summary>
        private static Dictionary<string, Tuple<string[], string[]>> _controllerAndActionToRolesAndUsers = new Dictionary<string, Tuple<string[], string[]>>();
    
    
        private static Tuple<string[], string[]> GetAuthRolesAndUsers(string actionName, string controllerName)
        {
            var controllerAndAction = controllerName + "~~" + actionName;
            if (_controllerAndActionToRolesAndUsers.ContainsKey(controllerAndAction))
                return _controllerAndActionToRolesAndUsers[controllerAndAction];
    
            Type controllerType = GetControllerType(controllerName);
            MethodInfo matchingMethodInfo = null;
    
            foreach (MethodInfo method in controllerType.GetMethods())
            {
                if (method.GetCustomAttributes(typeof(HttpPostAttribute), true).Any())
                    continue;
                if (method.GetCustomAttributes(typeof(HttpPutAttribute), true).Any())
                    continue;
                if (method.GetCustomAttributes(typeof(HttpDeleteAttribute), true).Any())
                    continue;
    
                var actionNameAttr = method.GetCustomAttributes(typeof(ActionNameAttribute), true).Cast<ActionNameAttribute>().FirstOrDefault();
                if ((actionNameAttr == null && method.Name == actionName) || (actionNameAttr != null && actionNameAttr.Name == actionName))
                {
                    matchingMethodInfo = method;
                }
            }
    
            if (matchingMethodInfo == null)
                return new Tuple<string[], string[]>(new string[0], new string[0]);
    
            var authAttrs = new List<AuthorizeAttribute>();
            authAttrs.AddRange(controllerType.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>());
    
            var roles = new List<string>();
            var users = new List<string>();
    
            foreach(var authAttr in authAttrs)
            {
                roles.AddRange(authAttr.Roles.Split(','));
                users.AddRange(authAttr.Roles.Split(','));
            }
    
            var rolesAndUsers = new Tuple<string[], string[]>(roles.ToArray(), users.ToArray());
            try
            {
                _controllerAndActionToRolesAndUsers.Add(controllerAndAction, rolesAndUsers);
            }
            catch (System.ArgumentException ex)
            {
                //possible but unlikely that two threads hit this code at the exact same time and enter a race condition
                //instead of using a mutex, we'll just swallow the exception when the method gets added to dictionary 
                //for the second time. mutex only allow single worker regardless of which action method they're getting
                //auth for. doing it this way eliminates permanent bottleneck in favor of a once in a bluemoon time hit
            }
    
            return rolesAndUsers;
        }
    
        public static bool IsAccessibleToUser(string actionName, string controllerName)
        {
            var rolesAndUsers = GetAuthRolesAndUsers(actionName, controllerName);
            var roles = rolesAndUsers.Item1;
            var users = rolesAndUsers.Item2;
    
            IPrincipal principal = HttpContext.Current.User;
    
            if (!roles.Any() && !users.Any() && principal.Identity.IsAuthenticated)
                return true;
    
    
            foreach (string role in roles)
            {
                if (role == "*" || principal.IsInRole(role))
                    return true;
            }
            foreach (string user in users)
            {
                if (user == "*" && (principal.Identity.Name == user))
                    return true;
            }
    
            return false;
        }
    
        public static Type GetControllerType(string controllerName)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (Type type in assembly.GetTypes())
            {
                if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
                {
                    return type;
                }
            }
            return null;
        }
    
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-08
      • 1970-01-01
      • 2015-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-23
      相关资源
      最近更新 更多