我正在为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”,否则返回空字符串。
但是在互联网上搜索了几天之后,我无法理解。 :(
到目前为止,我只找到了这个"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
}
我正在寻找获取动作属性值很长时间的解决方案,根本找不到足够的好资源。我错过了正确的关键字吗? :/
如果有人能为我提供真正有用的解决方案。 提前感谢您的解决方案
答案 0 :(得分:14)
虽然我同意根据权限生成网址可能不是最佳做法,但如果您仍想继续操作,可以使用以下方法找到操作及其属性:
检索'动作'方法: 这将检索方法信息的集合,因为可能有多个具有相同名称的Controller类和多个同名的方法,特别是使用区域。如果你不得不担心这一点,我会把消除歧义留给你。
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;
}
关于根据权限生成网址的说明:
我声明这可能可能不是最佳实践,因为有许多小事情。根据您的具体情况,每个人可能都有自己的相关程度。
通过操作授权属性控制页面渲染:
将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/>
}
答案 1 :(得分:2)
我不认为您每次要使用Url.Action()创建网址时都应该检查操作注释。如果使用自定义授权过滤器保护操作,则不会为非特权用户执行该操作,那么将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目的 - 访问的真正控制权由自定义授权属性完成。
答案 2 :(得分: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;
}
然后你在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)
}
现在,Authorize Attribute和Views都使用相同的角色/权限数据逻辑。