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

10

我正在为我的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”权限,则返回指向“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时验证用户权限吗? - Mr.Mindor
没事了,我第一遍读漏了 AuthorizeCore... - Mr.Mindor
3个回答

16

虽然我同意基于权限生成URL可能不是最佳实践,但如果您仍然想要继续,可以使用以下方法找到操作及其属性:

检索“Action”方法: 这将检索方法信息的集合,因为可能有多个具有相同名称的控制器类和特定方法的多个实例,尤其是在使用区域的情况下。如果您需要担心这一点,我会将消歧义留给您。

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扩展: 警告:如果您对于给定的控制器/操作对有多个匹配项,则此扩展将在用户被授权访问任何一个匹配项时提供“已授权”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 是否不显示是因为权限不正确,还是存在其他 bug?
  • AuthorizedAction 方法的行为似乎不一致。有时返回 URL,有时返回空字符串。

通过操作授权属性控制页面呈现:AuthorizedAction 方法修改为一个布尔值,然后使用该结果来控制页面呈现。

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")){
   <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不是最佳实践,并介绍我可以遵循的替代程序,那就太好了。 - Burhan Uddin
正如我在另一个答案中所评论的,“我的观点是,如果MyAuthorization注释权限值更改了操作,我必须在使用URL的所有UI中都进行更改”,这就是我不想做的。 - Burhan Uddin
@Burhan 空的url在你的目标中具体有什么意义? - Mr.Mindor
我正在尝试隐藏视图页面中动作的URL,以防那些没有访问权限的用户看到。我可以在Razor视图中使用if else,但如果对某个操作的权限更改,则需要更改这些条件。 - Burhan Uddin
@Burhan,我仍然不确定你认为仅隐藏URL会带来什么好处,特别是如果它会破坏你的用户界面。 - Mr.Mindor
1
@Burhan 关于需要更改Razor以及权限的问题:请参考此答案的最后一部分:通过操作授权属性控制页面呈现:Html.AuthorizedAction采用与Url.Action相同的控制器和操作方式。如果经过授权,则返回true,否则返回false。随着对操作的权限更改,它不需要更新Razor以保持一致。 - Mr.Mindor

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 Uddin
@Burhan,也许将(ControllerName,ActionName)作为权限定义来处理会有所帮助。每个控制器+操作都将定义一些细粒度的权限描述。缺点是会有很多权限,但您可以将它们分组到角色中。作为优势-您不需要使用权限注释操作,因为它们将是自我描述的。 - PanJanek

0

我的唯一建议是,编写一个扩展方法在 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;
}

那么你在视图/局部视图中的代码更容易阅读,因为它实际上是在做什么(不涉及任何HTML,而是验证用户),然后视图/局部视图中的代码看起来像:

@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 中进行更改。 :( - Burhan Uddin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接