如何在ASP.NET MVC 4中获取控制器操作的自定义注释属性?

我正在为ASP.NET MVC中的应用程序使用基于权限的授权系统。 为此,我创建了一个自定义授权属性

public class MyAuthorizationAttribute : AuthorizeAttribute { string Roles {get; set;} string Permission {get; set;} } 

这样我就可以通过角色或具有注释的特定权限密钥来授权用户

 public class UserController : Controller { [MyAuthorization(Roles="ADMIN", Permissions="USER_ADD")] public ActionResult Add() [MyAuthorization(Roles="ADMIN", Permissions="USER_EDIT")] public ActionResult Edit() [MyAuthorization(Roles="ADMIN", Permissions="USER_DELETE")] public ActionResult Delete() } 

然后我在MyAuthorizationAttribute类中覆盖具有类似逻辑的AuthorizeCore()方法(伪代码)

 protected override bool AuthorizeCore(HttpContextBase httpContext) { if(user not authenticated) return false; if(user has any role of Roles) return true; if(user has any permission of Permissions) return true; return false; } 

到目前为止工作正常。

现在我需要某种扩展方法,以便我可以在视图页面中动态生成动作URL,该视图页面将根据给定动作的MyAuthorization属性授权逻辑返回动作URL。 喜欢

 @Url.MyAuthorizedAction("Add", "User") 

如果用户具有admin角色或具有“USER_ADD”权限(如操作的属性中所定义),则将URL返回到“User / Add”,否则返回空字符串。

但在互联网上搜索了几天后,我无法理解。 🙁

到目前为止,我只找到了这个“安全感知”动作链接? 通过执行操作的所有操作filter直到失败为止。

这很好,但我认为每次调用MyAuthorizedAction()方法时执行所有动作filter都会产生开销。 此外它也不适用于我的版本(MVC 4和.NET 4.5)

我需要的是检查经过身份validation的用户的角色,权限(将存储在会话中)与授权角色和给定操作的权限。 喜欢以下内容(伪代码)

 MyAuthorizedAction(string actionName, string controllerName) { ActionObject action = SomeUnknownClass.getAction(actionName, controllerName) MyAuthorizationAttribute attr = action.returnsAnnationAttributes() if(user roles contains any in attr.Roles or user permissions contains any attr.Permissions) { return url to action } return empty string } 

我正在寻找获取动作属性值很长时间的解决方案,根本找不到足够的好资源。 我错过了正确的关键字吗? :/

如果有人能为我提供真正有用的解决方案。 在此先感谢您的解决方案

虽然我同意根据权限生成url可能不是最佳做法,但如果您仍想继续,可以使用以下方法找到操作及其属性:

检索’Action’方法:这将检索方法信息的集合,因为可以使用多个具有相同名称的Controller类和多个同名方法,特别是使用区域。 如果你不得不担心这一点,我会把消除歧义留给你。

 public static IEnumerable GetActions(string controller, string action) { return Assembly.GetExecutingAssembly().GetTypes() .Where(t =>(t.Name == controller && typeof(Controller).IsAssignableFrom(t))) .SelectMany( type => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(a => a.Name == action && a.ReturnType == typeof(ActionResult)) ); } 

从MyAuthorizationAttributes检索权限:

 public static MyAuthorizations GetMyAuthorizations(IEnumerable actions) { var myAuthorization = new MyAuthorizations(); foreach (var methodInfo in actions) { var authorizationAttributes = methodInfo .GetCustomAttributes(typeof (MyAuthorizationAttribute), false) .Cast(); foreach (var myAuthorizationAttribute in authorizationAttributes) { myAuthorization.Roles.Add(MyAuthorizationAttribute.Role); myAuthorization.Permissions.Add(MyAuthorizationAttribute.Permission); } } return myAuthorization; } public class MyAuthorizations { public MyAuthorizations() { Roles = new List(); Permissions = new List(); } public List Roles { get; set; } public List Permissions { get; set; } } 

最后是AuthorizedAction扩展:警告:如果你确实有一个给定控制器/动作对的匹配,如果用户被授权任何一个,那么这将给出’授权’url …

 public static string AuthorizedAction(this UrlHelper url, string controller, string action) { var actions = GetActions(controller, action); var authorized = GetMyAuthorizations(actions); if(user.Roles.Any(userrole => authorized.Roles.Any(role => role == userrole)) || user.Permissions.Any(userPermission => authorized.Permissions.Any(permission => permission == userPermission))) { return url.Action(controller,action) } return string.empty; } 

基于权限生成URL的注意事项:
我说这可能不是最好的做法,因为有很多小事情。 根据您的具体情况,每个人可能都有自己的相关程度。

  • 给人的印象是试图通过默默无闻来实现安全。 如果我没有向他们展示url,他们就不会知道它在那里。
  • 如果您已经通过其他方式检查权限来控制页面的呈现(因为看起来您正在根据其他地方的评论进行操作),那么明确不写出url是毫无意义的。 最好不要调用Url.Action方法。
  • 如果您尚未根据用户的权限控制页面的呈现,则只需为URL添加空字符串将在页面上留下大量破损或看似破碎的内容。 按下它时,这个按钮没有任何作用!
  • 它可以使测试和调试更复杂: 由于权限不正确,或者是否有其他错误,url是否显示?
  • AuthorizedAction方法的行为似乎不一致。 有时返回一个url,其他时候返回一个空字符串。

通过操作授权控制页面渲染属性:AuthorizedAction方法修改为boolean ,然后使用其结果来控制页面渲染。

 public static bool AuthorizedAction(this HtmlHelper helper, string controller, string action) { var actions = GetActions(controller, action); var authorized = GetMyAuthorizations(actions); return user.Roles.Any(userrole => authorized.Roles.Any(role => role == userrole)) || user.Permissions.Any(userPermission => authorized.Permissions.Any(permission => permission == userPermission)) } 

然后在你的razor页面中使用它。

 @if(Html.AuthorizedAction("User","Add")){ 
If you see this, you have permission to add a user.
etc
} else { }

我不认为您每次要使用Url.Action()创建URL时都应检查操作注释。 如果使用自定义授权filter保护操作,则不会为非特权用户执行该操作,那么将URL隐藏到该操作的重点是什么? 相反,您可以在HtmlHelper上实现扩展方法,以检查当前用户是否具有给定的预设,例如:

 public static bool HasPermission(this HtmlHelper helper, params Permission[] perms) { if (current user session has any permission from perms collection) { return true; } else { return false; } } 

然后,您可以使用视图中的帮助程序隐藏当前用户无法访问的按钮和链接,例如:

 @if (Html.HasPermission(Permission.CreateItem)) { Create item } 

当然,隐藏特定链接仅用于UI目的 – 访问的真正控制权由自定义授权属性完成。

我唯一的建议是在IPrincipal上编写扩展方法,而不是看起来像

 public static bool HasRolesAndPermissions(this IPrincipal instance, string roles, string permissions,) { if(user not authenticated) return false; if(user has any role of Roles) return true; if(user has any permission of Permissions) return true; return false; } 

那么你在views / partials中的代码就它实际做的事情而言更具可读性(不用html做任何事情,但validation用户)那么views / partials中的代码看起来像

 @if (User.HasRolesAndPermissions(roles, permissions)) { @Html.ActionLink(..); } 

每个MVC页面都有当前用户的属性WebViewPage.User 。

您的目标解决方案(以及与安全感知链接的链接)的问题在于链接的创建和控制器上的授权可能不同(在我看来,这种类型的混合责任是不好的做法)。 通过扩展IPrincipal ,新的授权看起来像:

 protected override bool AuthorizeCore(HttpContextBase httpContext) { return user.HasRolesAndPermissions(roles, permissions) } 

现在,Authorize Attribute和Views都使用相同的角色/权限数据逻辑。