【问题标题】:mvcSiteMap Bootstrap showing links based on user roles without using User.IsInRolemvcSiteMap Bootstrap 显示基于用户角色的链接而不使用 User.IsInRole
【发布时间】:2018-01-24 19:38:44
【问题描述】:

首先,为垃圾问题的标题道歉。

基于示例here,我有一个菜单,它根据用户角色显示导航子菜单链接。这是 mvc.sitemap 文件中的一行。 'userRole' 是一个自定义属性,用于指示哪个角色可以看到此 url。

    <mvcSiteMapNode title="Customers" controller="Home" action="Index" 
area="Customers" userRole="Customers">

在 BootstrapMenuHelperModel.cshtml 中(同样基于上面的示例),我有一行包含:

  if ( User.IsInRole(node.Attributes["userRole"].ToString())
    {
     ...show url

决定是否显示节点/导航项项。

这一切都很好。但是,每次我调用 User.IsInRole() 时都会访问数据库,我想通过将用户角色的集合传递给 View 来避免这种情况,然后我可以使用它来检查。视图 @model MenuHelperModel 的模型在 MvcSiteMapProvider.dll 中,我无法修改它,因为它是 Nuget 包的一部分。

所以问题是:我如何访问或以某种方式将当前用户的角色传递给视图,同时牢记我无法修改视图的模型?

【问题讨论】:

  • 如果您无法更改模型,我会将它们通过 viewbag 传递

标签: asp.net-mvc twitter-bootstrap asp.net-mvc-4 mvcsitemapprovider


【解决方案1】:

安全修整是 MvcSiteMapProvider 的内置功能。如果您使用 AuthorizeAttribute 来定义哪些操作具有,它将自动隐藏用户无权访问的节点。

默认情况下禁用安全修整。您需要做的第一件事就是启用它。

内部 DI (web.config):

<add key="MvcSiteMapProvider_SecurityTrimmingEnabled" value="true"/>

外部 DI(在 MvcSiteMapProvider 模块中):

bool securityTrimmingEnabled = true; // First line in the module

然后您应该将 MVC [Authorize] 属性放在您想要保护的每个操作方法上。在 MVC4 或更高版本中,建议您在全局注册 [AuthoirizeAttribute],然后使用 [AllowAnonymous] 属性选择性地允许未经身份验证的用户允许操作方法。这将确保您拥有白名单安全而非黑名单安全。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new AuthorizeAttribute());
    }
}

[Authorize(Roles="Admin,Manager")]
public class MyController
{
    // Everyone has access
    [AllowAnonymous]
    public ActionResult Index()
    {
        return View();
    }

    // Only Admin and Manager roles have access, everyone else is denied
    public ActionResult About()
    {
        return View();
    }
}

public class MyController2
{
    // Only Customers have access
    [Authorize(Roles="Customers")]
    public ActionResult Index()
    {
        return View();
    }

    // All authenticated users have access
    [Authorize]
    public ActionResult About()
    {
        return View();
    }
}

您的节点配置没有什么特别需要做的 - 安全性完全由控制器和操作完成。

设置完成后,不仅您的链接会被适当地隐藏,而且用户在导航到他们无权访问的控制器操作时也会获得 401 状态,并且 MVC 会自动将他们重定向到登录页面。

如果您正在尝试为 MVC 重塑安全性,还有其他选择。但请记住,这很可能不如使用 AuthorizeAttribute 安全。

选项 1

如果您在代码中构建节点(通过实现 IDynamicNodeProvider 或 ISiteMapNodeProvider)而不是使用 XML 来配置您的节点,您可以将您想要的任何对象类型作为自定义属性附加到节点。知道了这一点,您可能会创建一个包含在节点之间共享的逻辑的对象。因此,您可以调用包含您的角色的用户缓存或会话对象。

public class MyDynamicNodeProvider 
    : DynamicNodeProviderBase 
{ 
    public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node) 
    {
        // Object to get the current user's roles
        var userRoleRetriever = new UserRoleRetriever();

        using (var storeDB = new MusicStoreEntities())
        {
            // Create a node for each album 
            foreach (var album in storeDB.Albums.Include("Genre")) 
            { 
                DynamicNode dynamicNode = new DynamicNode(); 
                dynamicNode.Title = album.Title; 
                dynamicNode.ParentKey = "Genre_" + album.Genre.Name; 
                dynamicNode.RouteValues.Add("id", album.AlbumId);

                // Add the same instance of userRoleRetriever to every node
                dynamicNode.Attributes.Add("userRoleRetriever", userRoleRetriever);

                yield return dynamicNode;
            }
        }
    } 
}

public class UserRoleRetriever
{
    // This method will retrieve and cache each user's roles separately. 
    // You can tweak the cache timeouts and cache dependencies or change
    // to use session state if that is what you prefer.
    public IEnumerable<string> GetRoles()
    {
        var userId = User.Identity.Name;
        var key = "UserRoles_" + userName;
        var result = HttpContext.Cache[key];
        if (result == null)
        {
            result = // Retrieve list of roles here ;
            HttpContext.Cache.Insert(key, result);
        }
        return result;
    }

    public bool IsInRole(string roleName)
    {
        var roles = GetRoles();
        return roles.Contains(roleName);
    }
}

然后您可以调用自定义对象中的逻辑,方法是将其转换回您的对象类型。

var retriever = (UserRoleRetriever)node.Attributes["userRoleRetriever"];
var roles = retriever.GetRoles();
var isCustomer = retriever.IsInRole("Customers");

选项 2

构建自定义扩展方法来获取角色。

public static class HtmlHelperExtensions
{
    public static bool IsUserInRole(this HtmlHelper htmlHelper, string roleName)
    {
        var retriever = new UserRoleRetriever();
        return retriever.IsInRole(roleName);
    }
}

然后从你的角度调用它。

@var isCustomer = Html.IsUserInRole("Customers")

不管你怎么做,我认为你缺少的部分是缓存,以确保你的数据库不会在每个请求上都被命中。但具体缓存多长时间、缓存位置以及是否使用缓存或会话取决于您的要求和应用程序设计。

【讨论】:

    猜你喜欢
    • 2013-05-17
    • 2016-09-09
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-02-13
    • 2020-12-18
    • 2016-05-05
    • 2011-10-22
    相关资源
    最近更新 更多