正如 Dominick Baier 所指出的,我能够使用Thinktecture.IdentityModel
. 我已经在 Gist 中发布了代码。这个特定的功能似乎没有记录,但我能够将相关博客文章和文档中的点点滴滴拼凑在一起。我很感激任何反馈,因为我确信 IdentityModel 或 WIF 的某些角落可以减轻我的代码的影响。也就是说,这运行良好。让我们从一个示例用法开始:
剃刀
// This would likely be a partial view with per-user output caching
@Html.BuildNavigation(new List<NavigationItem>
{
new NavigationItem("Production", MVC.Production.Home.Index()),
new NavigationItem("Inventory", MVC.Inventory.Home.Index()),
new NavigationItem("Quality Control", MVC.QualityControl.Home.Index()),
new NavigationItem("Customers", MVC.Sales.Home.Index()),
new NavigationItem("Vendors", MVC.Vendors.Companies.Index()),
})
如下所示,BuildNavigation
辅助函数将呈现一个<ul>
包含用户有权查看的所有导航项的内容。如果您想知道这些MVC.{Area}.{Controller}.{Action}()
语句,这些是T4MVC助手。没有必要使用 T4MVC。为方便起见,NavigationItem
提供了一个接受 an 的覆盖ActionResult
。实际上,NavigationItem
仅由string
要显示的 a 和 a RouteValueDictionary
(参见要点)组成。
HTML 助手扩展
为了利用 Thinktecture.IdentityModel 提供的现有授权实用程序,您必须创建一个RequestContext
. RouteData
请注意,我们从构建RouteValueDictionary
,然后RequestContext
为授权助手创建一个新的。重要的旁注:如果您像我一样在 MVC 项目中使用区域,则该AddNamespaceInfo
功能至关重要。如果您在不同的区域有重复的控制器名称,那么控制器工厂将知道如何访问正确的名称。否则,您将收到异常。
public static class NavigationHelper
{
public static MvcHtmlString BuildNavigation(this HtmlHelper htmlHelper, IEnumerable<NavigationItem> navigationItems)
{
var container = new TagBuilder("ul");
container.MergeAttribute("id", "menu");
var innerHtmlBuilder = new StringBuilder();
foreach (var item in navigationItems.Where(item => IsAuthorized(htmlHelper, item.RouteValueDictionary)))
{
innerHtmlBuilder.Append(
new TagBuilder("li")
{
InnerHtml = htmlHelper.ActionLink(
item.LinkText,
item.RouteValueDictionary["action"] as string,
item.RouteValueDictionary["controller"] as string,
item.RouteValueDictionary, null).ToHtmlString()
});
}
container.InnerHtml = innerHtmlBuilder.ToString();
return new MvcHtmlString(container.ToString());
}
private static bool IsAuthorized(this HtmlHelper htmlHelper, RouteValueDictionary routeValues)
{
var routeData = BuildRouteData(htmlHelper.RouteCollection, routeValues);
var context = BuildRequestContext(htmlHelper, routeData);
return ClaimsAuthorizationHelper.CheckAccess(context);
}
private static RouteData BuildRouteData(IEnumerable<RouteBase> routeCollection, RouteValueDictionary routeValues)
{
object controllerValue;
routeValues.TryGetValue("controller", out controllerValue);
var controllerName = controllerValue as string;
object actionValue;
routeValues.TryGetValue("action", out actionValue);
var actionName = actionValue as String;
object areaValue;
routeValues.TryGetValue("area", out areaValue);
var areaName = areaValue as String ?? "";
var routeData = new RouteData();
routeData.Values.Add("action", actionName);
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("area", areaName);
AddNamespaceInfo(routeData, routeCollection, areaName, controllerName, actionName);
return routeData;
}
private static RequestContext BuildRequestContext(this HtmlHelper htmlHelper, RouteData routeData)
{
var claimsPrincipal = htmlHelper.ViewContext.HttpContext.User as ClaimsPrincipal;
var requestContext = new RequestContext(htmlHelper.ViewContext.HttpContext, routeData);
requestContext.HttpContext.User = claimsPrincipal;
return requestContext;
}
private static void AddNamespaceInfo(RouteData routeData, IEnumerable<RouteBase> routeCollection, string areaName, string controllerName, string actionName)
{
var route = routeCollection.GetRoute(areaName, controllerName, actionName);
if (route != null)
{
routeData.DataTokens.Add("Namespaces", route.DataTokens["Namespaces"]);
}
}
}
Thinktecture.IdentityModel ClaimsAuthorizeAttribute Wrapper
我遇到的另一个绊脚石是ClaimsAuthorizeAttribute
. 这是我怀疑通过更深入地了解 WIF 可能会消除的一个领域。但是,当时,我已经创建了一个包装器,ClaimsAuthorizeAttribute
它允许我将属性转换为声明。
public class ClaimsAuthorizeAttribute : Thinktecture.IdentityModel.Authorization.Mvc.ClaimsAuthorizeAttribute
{
private readonly string _action;
private readonly string[] _resources;
public ClaimsAuthorizeAttribute(string action, params string[] resources)
:base(action, resources)
{
_action = action;
_resources = resources;
}
public IEnumerable<Claim> GetClaims()
{
return _resources.Select(r => new Claim(_action, r));
}
}
索赔授权助手
最后,这里ClaimsAuthorizationHelper
负责解析必要的控制器和操作方法,检索资源声明,并调用ClaimsAuthorization
Thinktecture IdentityModel 提供的实用程序。
public static class ClaimsAuthorizationHelper
{
public static bool CheckAccess(RequestContext requestContext)
{
var routeData = requestContext.RouteData;
var controllerName = routeData.Values["controller"] as string;
var actionName = routeData.Values["action"] as string;
var controller = GetControllerByName(requestContext, controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var controllerContext = new ControllerContext(requestContext, controller);
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
var resourceClaims = actionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (ClaimsAuthorizeAttribute), false)
.Cast<ClaimsAuthorizeAttribute>()
.SelectMany(auth => auth.GetClaims()).ToList();
resourceClaims.AddRange(actionDescriptor.GetCustomAttributes(typeof(ClaimsAuthorizeAttribute), false).Cast<ClaimsAuthorizeAttribute>()
.SelectMany(c => c.GetClaims()));
var hasAccess = ClaimsAuthorization.CheckAccess(actionName, resourceClaims.ToArray());
return hasAccess;
}
public static ControllerBase GetControllerByName(RequestContext requestContext, string controllerName)
{
var factory = ControllerBuilder.Current.GetControllerFactory();
var controller = factory.CreateController(requestContext, controllerName);
if (controller == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The current controller factory, \"{0}\", did not return a controller for the name \"{1}\".", factory.GetType(), controllerName));
}
return (ControllerBase)controller;
}
}
其他代码
为简洁起见,还有一些其他的助手和类被省略了。请参阅 Gist 以获取完整代码。