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