ASP.NET MVC对基于角色的安全性有很好的支持,但是使用字符串作为角色名称是令人抓狂的,因为它们不能作为枚举强类型化。
例如,我的应用中有“管理员”角色。 “Admin”字符串现在将存在于我的操作的Authorize属性,我的母版页(用于隐藏选项卡),我的数据库(用于定义每个用户可用的角色)以及我的代码或视图中的任何其他位置我需要为管理员或非管理员用户执行特殊逻辑的文件。
是否有更好的解决方案,缺少编写我自己的授权属性和过滤器,这可能会处理一组枚举值?
答案 0 :(得分:48)
使用魔术字符串可以灵活地在Authorize属性中声明多个角色(例如[Authorize(Roles =“Admin,Moderator”)],当您使用强类型解决方案时,这些角色往往会丢失。但是这就是你的方式可以保持这种灵活性,同时仍然可以输入所有强类型。
在使用位标志的枚举中定义您的角色:
[Flags]
public enum AppRole {
Admin = 1,
Moderator = 2,
Editor = 4,
Contributor = 8,
User = 16
}
覆盖AuthorizeAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute {
public AppRole AppRole { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext) {
if (AppRole != 0)
Roles = AppRole.ToString();
base.OnAuthorization(filterContext);
}
}
现在,如果您可以像这样使用MyAuthorizeAttribute:
[MyAuthorize(AppRole = AppRole.Admin | AppRole.Moderator | AppRole.Editor)]
public ActionResult Index() {
return View();
}
以上操作仅授权至少列出其中一个角色的用户(管理员,主持人或编辑者)。行为与MVC的默认AuthorizeAttribute相同,除非没有魔术字符串。
如果您使用此技术,这里是IPrincipal的扩展方法,也可能有用:
public static class PrincipalExtensions {
public static bool IsInRole(this IPrincipal user, AppRole appRole) {
var roles = appRole.ToString().Split(',').Select(x => x.Trim());
foreach (var role in roles) {
if (user.IsInRole(role))
return true;
}
return false;
}
}
您可以像这样使用此扩展方法:
public ActionResult Index() {
var allowed = User.IsInRole(AppRole.Admin | AppRole.Moderator | AppRole.Editor);
if (!allowed) {
// Do Something
}
return View();
}
答案 1 :(得分:21)
我通常使用带有一堆字符串常量的类。这不是一个完美的解决方案,因为你需要记住坚持在任何地方使用它,但至少它摆脱了拼写错误的可能性。
static class Role {
public const string Admin = "Admin";
}
答案 2 :(得分:10)
虽然它不使用枚举,但我使用了下面的解决方案,我们将Authorize过滤器子类化为构造函数中的可变长度角色名称参数。将它与const变量中声明的角色名一起使用,我们避免使用魔术字符串:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params string[] roles) : base()
{
Roles = string.Join(",", roles);
}
}
public class MyController : Controller
{
private const string AdministratorRole = "Administrator";
private const string AssistantRole = "Assistant";
[AuthorizeRoles(AdministratorRole, AssistantRole)]
public ActionResult AdminOrAssistant()
{
return View();
}
}
(我在稍微详细的博客中提到了这一点 - http://tech-journals.com/jonow/2011/05/19/avoiding-magic-strings-in-asp-net-mvc-authorize-filters)
答案 3 :(得分:3)
根据您的建议,
对其进行子类型,为枚举类型添加自定义属性,并对传递的值调用AuthorizeAttribute
。把它放在常规角色属性中。这应该只需几行代码,ToString()
仍然可以完成所有实际工作。
对于Matti也是+1,因为consts也是一个不错的选择。
答案 4 :(得分:3)
我接受了JohnnyO的回复,但更改了枚举项以使用DescriptionAttribute
来指定角色的字符串值。如果您希望角色字符串与枚举名称不同,这会派上用场。
枚举示例:
[Flags]
public enum AppRole
{
[Description("myRole_1")]
RoleOne = 1,
[Description("myRole_2")]
RoleTwo = 2
}
扩展方法:
public static bool IsInRole(this IPrincipal user, AppRole appRole)
{
var roles = new List<string>();
foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole)))
if ((appRole & role) != 0)
roles.Add(role.ToDescription());
return roles.Any(user.IsInRole);
}
自定义属性:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AppAuthorizeAttribute : AuthorizeAttribute
{
public AppRole AppRoles { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
var roles = new List<string>();
foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole)))
if((AppRoles & role) != 0)
roles.Add(role.ToDescription());
if (roles.Count > 0)
Roles = string.Join(",", roles);
base.OnAuthorization(filterContext);
}
}
获取描述值的扩展方法:
public static string ToDescription(this Enum value)
{
var da = (DescriptionAttribute[])
(value.GetType().GetField(value.ToString()))
.GetCustomAttributes(typeof (DescriptionAttribute), false);
return da.Length > 0 ? da[0].Description : value.ToString();
}
答案 5 :(得分:2)
我使用了一个静态类来定义Matti建议的一堆字符串常量,在我当前的项目中,我使用下面的扩展方法和枚举。这两种方法都运作良好。
public static class EnumerationExtension
{
public static string GetName(this Enum e)
{
return Enum.GetName(e.GetType(), e);
}
}