代码之家  ›  专栏  ›  技术社区  ›  Peter Gfader

“安全意识”行动链接?

  •  21
  • Peter Gfader  · 技术社区  · 16 年前

    如何创建一个“安全感知”操作链接来检测用户是否有权单击(调用)该操作?
    如果不允许用户使用该操作,则隐藏链接…

    取决于

    • web.config(授权)和
    • [授权]操作属性

    聚苯乙烯
    我想在MVC中混合这两个是不好的做法?

    3 回复  |  直到 11 年前
        1
  •  30
  •   Keith Sirmons    11 年前

    这是从MVCSitemap项目中提取的一些代码,并进行了修改以供我自己使用。如果我没记错的话,这个代码已经为MVC2修改过了,其中一些函数可能需要重新移植到MVC1。

    将MVC和FormsAuthentication混合在一起是一种不错的做法,MVC的默认身份验证方法是围绕现有的ASP.NET安全基础结构构建的。

    用于确定用户是否具有权限的代码:

    public static class SecurityTrimmingExtensions 
    {
    
        public static bool HasActionPermission( this HtmlHelper htmlHelper, string actionName, string controllerName )
        {
            //if the controller name is empty the ASP.NET convention is:
            //"we are linking to a different controller
            ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) 
                                                    ? htmlHelper.ViewContext.Controller
                                                    : GetControllerByName(htmlHelper, controllerName);
    
            var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo);
    
            var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType());
    
            var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
    
            return ActionIsAuthorized(controllerContext, actionDescriptor);
        }
    
    
        private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
                return false; // action does not exist so say yes - should we authorise this?!
    
            AuthorizationContext authContext = new AuthorizationContext(controllerContext);
    
            // run each auth filter until on fails
            // performance could be improved by some caching
            foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters)
            {
                authFilter.OnAuthorization(authContext);
    
                if (authContext.Result != null)
                    return false;
            }
    
            return true;
        }
    
        private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName)
        {
            // Instantiate the controller and call Execute
            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
    
            IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName);
    
            if (controller == null)
            {
                throw new InvalidOperationException(
    
                    String.Format(
                        CultureInfo.CurrentUICulture,
                        "Controller factory {0} controller {1} returned null",
                        factory.GetType(),
                        controllerName));
    
            }
    
            return (ControllerBase)controller;
        }
    
    }
    

    HTML帮助程序

    public static class SecurityTrimmedLink
    {
        public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName)
        {
            return htmlHelper.HasActionPermission(actionName, "")
                       ? htmlHelper.ActionLink(linkName, actionName)
                       : MvcHtmlString.Create("");
        }        
    
        public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, RouteValueDictionary routeValueDictionary )
        {
            return htmlHelper.HasActionPermission(actionName, "")
                       ? htmlHelper.ActionLink(linkName, actionName, routeValueDictionary)
                       : MvcHtmlString.Create("");
        }
    
        public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, object routeValues, object htmlAttributes )
        {
            return htmlHelper.HasActionPermission(actionName, "")
                       ? htmlHelper.ActionLink(linkName, actionName, routeValues, htmlAttributes)
                       : MvcHtmlString.Create("");
        }
    
        public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, string controllerName)
        {
            return htmlHelper.HasActionPermission(actionName, controllerName)
                       ? htmlHelper.ActionLink(linkName, actionName, controllerName)
                       : MvcHtmlString.Create("");
        }
    
        public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, string controllerName, object routeValues, object htmlAttributes)
        {
            return htmlHelper.HasActionPermission(actionName, controllerName)
                       ? htmlHelper.ActionLink(linkName, actionName, controllerName, routeValues, htmlAttributes)
                       : MvcHtmlString.Create("");
        }
    }
    

    警告:这在MVC 5中不起作用,因为对findaction()的调用从不返回操作描述符

    我试图找到这个问题,但却找不到,最后我编了一个解决方案。:(

        2
  •  6
  •   viggity    13 年前

    JFAR的代码在很大程度上对我有用,但我必须对MVC4做一些修改。这是唯一必须更改的方法:

    private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (actionDescriptor == null)
            return false; // action does not exist so say yes - should we authorise this?!
    
        AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
    
        // run each auth filter until on fails
        // performance could be improved by some caching
        foreach (var filter in FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor))
        {
            var authFilter = filter.Instance as IAuthorizationFilter;
    
            if (authFilter == null)
                continue;
    
            authFilter.OnAuthorization(authContext);
    
            if (authContext.Result != null)
                return false;
        }
    
        return true;
    }
    
        3
  •  2
  •   Jamee    14 年前

    区域部分比添加一些重载要复杂一些。 useNamespaceFallback hack不起作用,因为当您在不同区域具有相同名称的控制器时,您将实例化错误的控制器。

    您需要有一种方法来获得该区域的正确命名空间

    否则这个

     IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName); 
    

    会出错的。

    目前我在视图中有这样的链接

    @Html.SecurityTrimmedActionLink("this link", "Index", "Home",new {Area=string.Empty});   
    @Html.SecurityTrimmedActionLink("this link", "Index", "FunctionAdministration", new   {Area="Administration" }, null);
    

    里面

    public static bool HasActionPermission(this HtmlHelper htmlHelper, string actionName, string controllerName, object area)
    

    当区域为空时,我将获取该区域的命名空间或默认命名空间。

    private static string GetNamespaceForArea(string area, RouteCollection routeColl)
        {
            string ns = string.Empty;
            foreach (RouteBase routeBase in routeColl)
            {
                if (routeBase.GetType() == (typeof (Route)))
                {
                    Route route = (Route) routeBase;
                    RouteValueDictionary dataTokens = route.DataTokens;
                    ;
                    if (area != null)
                    {
                        if (dataTokens.ContainsKey("area"))
                        {
                            if (area.Equals(dataTokens["area"]))
                            {
                                ns = (string) ((string[]) dataTokens["Namespaces"])[0];
                                break;
                            }
                        }
                        else
                        {
                            if (area.Equals(string.Empty))
                            {
                                ns = (string) ((string[]) dataTokens["Namespaces"])[0];
                                break;
                            }
                        }
                    }
                }
            }
            return ns;
        }
    

    例如,您需要在GlobalSAX中设置路由中的默认命名空间(默认ns“actionLinkTest.controllers”):

    routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
                , new[] { "ActionLinkTest.Controllers" }
            );
    

    使用它根据类型名称创建控制器:

    ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) ? htmlHelper.ViewContext.Controller :(ControllerBase) Activator.CreateInstance(Type.GetType(type));
    

    在global.asax中定义区域

    areaRegistration.Add("Administration","Areas.Administration");