【问题标题】:How to get custom annotation attributes for a controller action in ASP.NET MVC 4?如何在 ASP.NET MVC 4 中获取控制器操作的自定义注释属性?
【发布时间】:2012-11-02 18:45:57
【问题描述】:

我正在为我的应用程序在 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,该操作 url 将基于给定操作的 MyAuthorization 属性授权逻辑返回操作 url。喜欢

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

如果用户具有管理员角色或具有“USER_ADD”权限(在操作的属性中定义),则将 url 返回到“用户/添加”,否则返回空字符串。

但在互联网上搜索了几天后,我无法弄清楚。 :(

到目前为止,我只发现了这个"Security aware" action link?,它通过执行该操作的所有操作过滤器直到失败为止。

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

我所需要的只是检查经过身份验证的用户的角色、权限(将存储在会话中)与授权角色和给定操作的权限。如下所示(伪代码)

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 时验证用户权限?
  • 没关系,我错过了 AuthorizeCore 第一次通读...

标签: c# asp.net-mvc razor


【解决方案1】:

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

检索“操作”方法: 这会检索方法信息的集合,因为可能有多个具有相同名称的控制器类和多个具有相同名称的方法,特别是在使用区域时。如果您不得不担心这一点,我将把歧义留给您。

public static IEnumerable<MethodInfo> 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<MethodInfo> actions)
{
    var myAuthorization = new MyAuthorizations();
    foreach (var methodInfo in actions)
    {
        var authorizationAttributes =  methodInfo
                .GetCustomAttributes(typeof (MyAuthorizationAttribute), false)
                .Cast<MyAuthorizationAttribute>();
        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<string>();
        Permissions = new List<string>();
    }
    public List<string> Roles { get; set; }
    public List<string> Permissions { get; set; }
}

最后是 AuthorizedAction 扩展: 警告:如果对于给定的控制器/动作对,您确实有多个匹配项,如果用户被授权其中任何一个,这将给出“授权”网址。 ..

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,他们就不会知道它在那里。
  • 如果您已经在以其他方式检查权限以控制页面的呈现(因为您正在根据其他地方的 cmets 执行此操作),那么明确不写出 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))
}

然后在您的剃须刀页面中使用它。

@if(Html.AuthorizedAction("User","Add")){
   <div id='add-user-section'>
        If you see this, you have permission to add a user.
        <form id='add-user-form' submit='@Url.Action("User","Add")'>
             etc
        </form>
   </div>
}
else {
  <some other content/>

}

【讨论】:

  • 非常感谢。您解释了我完全缺少的关于获取操作及其注释属性的项目。如果您还解释为什么基于权限生成 url 不是最佳实践以及我可以遵循哪些替代程序,那就太好了
  • 正如我在另一个答案中评论的那样,“我的意思是,如果 MyAuthorization 注释权限值更改了操作,我必须在我使用 url 的整个 ui 上进行更改”这就是我所做的不想做。
  • @Burhan 使用空网址的具体目标是什么?
  • 我正在尝试对那些无权访问该操作的用户隐藏视图页面中操作的 URL。我可以在 razor 视图中使用 if else,但如果操作权限发生更改,我将需要更改这些条件
  • @Burhan 至于是否需要更改剃须刀以及权限:请参阅本答案的最后一部分:通过动作授权属性控制页面渲染:Html.AuthorizedAction 采用与 Url.Action 相同的控制器和动作做。如果授权为假,则返回真,否则返回真。随着操作权限的变化,它不需要更新剃须刀来保持一致。
【解决方案2】:

我认为你不应该在每次想要使用 Url.Action() 创建 url 时检查动作注释。如果该操作使用自定义授权过滤器进行保护,它将不会为非特权用户执行,那么隐藏该操作的 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))
{
    <a href="@Url.Action("Items", "Create")">Create item</a>
}

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

【讨论】:

  • 谢谢,这正是我现在正在做的。我只是想找出是否有一种方法可以从给定的操作 url 动态获取权限密钥,而不是每次都将其作为参数传递给扩展方法
  • @Burhan,也许将 (ControllerName,ActionName) 对视为权限定义就可以了。每个控制器+动作都会定义一些细粒度的权限描述。缺点是会有很多权限,但是你可以将它们分组到角色中。作为一个优势 - 您不需要使用权限注释操作,因为它们是自我描述的。
【解决方案3】:

我唯一的建议是在 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做任何事情,而是验证用户)更具可读性,然后views/partials中的代码看起来像

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

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

您有针对性的解决方案(以及指向安全意识链接的链接)的问题在于,链接的创建和控制器上的授权可能不同(在我看来,以这种方式混合职责是不好的做法)。通过扩展IPrincipal,新的授权将如下所示:

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

现在您的授权属性和视图都使用相同的角色/权限数据逻辑。

【讨论】:

  • 谢谢,我现在正在做类似的事情,我的意思是如果 MyAuthorization 注释权限值发生变化,我必须在我使用 url 的整个 ui 上进行更改。 :(
猜你喜欢
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-09
相关资源
最近更新 更多