我最近遇到了完全相同的问题。我最终选择了一个继承自 AuthorizeAttribute 的自定义 ActionFilter。
它基本上具有与Authorize 相同的功能(检查用户是否至少属于列出的角色之一),但还增加了检查用户是否“拥有”特定数据的功能。
以下是供您用作示例的代码。如果有什么不清楚的地方,请评论,我会尽力解释。
[编辑 - 根据 Ryan 的建议,我将 params UserRole[] 设为构造函数参数而不是公共属性,并添加了 AllowAnyRolesIfNoneSpecified。]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AccountAuthorizeAttribute : AuthorizeAttribute
{
private readonly UserRole[] _userRoles;
public bool MustBeInRoleOrPageOwner { get; set; }
public bool MustBeInRoleAndPageOwner { get; set; }
public bool MustBeInRoleAndNotPageOwner { get; set; }
public bool AllowAnyRolesIfNoneSpecified { get; set; }
public string AccessDeniedViewName { get; set; }
public AccountAuthorizeAttribute(params UserRole[] userRoles)
{
_userRoles = userRoles;
MustBeInRoleOrPageOwner = false;
MustBeInRoleAndPageOwner = false;
MustBeInRoleAndNotPageOwner = false;
AllowAnyRolesIfNoneSpecified = true;
AccessDeniedViewName = "AccessDenied";
}
protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
ShowLogOnPage(filterContext);
return;
}
using (var dbContext = new MainDbContext())
{
var accountService = new AccountService(dbContext);
var emailAddress = filterContext.HttpContext.User.Identity.Name;
if (IsUserInRole(accountService, emailAddress))
{
var isPageOwner = IsUserPageOwner(filterContext, dbContext, accountService, emailAddress);
if (MustBeInRoleAndPageOwner && !isPageOwner || MustBeInRoleAndNotPageOwner && isPageOwner)
ShowAccessDeniedPage(filterContext);
}
else
{
if (!MustBeInRoleOrPageOwner)
ShowAccessDeniedPage(filterContext);
else if (!IsUserPageOwner(filterContext, dbContext, accountService, emailAddress))
ShowAccessDeniedPage(filterContext);
}
}
}
private bool IsUserInRole(AccountService accountService, string emailAddress)
{
if (_userRoles.Length == 0 && AllowAnyRolesIfNoneSpecified) return true;
return accountService.IsUserInRole(emailAddress, _userRoles);
}
protected virtual bool IsUserPageOwner(
AuthorizationContext filterContext, MainDbContext dbContext, AccountService accountService, string emailAddress)
{
var id = GetRouteId(filterContext);
return IsUserPageOwner(dbContext, accountService, emailAddress, id);
}
protected int GetRouteId(AuthorizationContext filterContext)
{
return Convert.ToInt32(filterContext.RouteData.Values["id"]);
}
private bool IsUserPageOwner(MainDbContext dbContext, AccountService accountService, string emailAddress, int id)
{
return accountService.IsUserPageOwner(emailAddress, id);
}
private void ShowLogOnPage(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
private void ShowAccessDeniedPage(AuthorizationContext filterContext)
{
filterContext.Result = new ViewResult { ViewName = "ErrorPages/" + AccessDeniedViewName };
}
private void PreventPageFromBeingCached(AuthorizationContext filterContext)
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
}
几个注意事项:
为了避免“魔术字符串”,我使用了 UserRole 枚举值数组而不是单个字符串。另外,我构建它是为了处理我遇到的几种情况:
- 用户必须是一个角色或页面/数据的所有者(例如,管理员可以编辑任何人的数据,任何人都可以编辑自己的数据)
- 用户必须是一个角色并且页面/数据的所有者(例如,用户只能编辑他/她自己的页面/数据——通常在没有任何角色限制的情况下使用)李>
- 用户必须是一个角色,并且不是页面/数据的所有者(例如,管理员可以编辑除他自己以外的任何人的页面/数据 - 例如,防止管理员删除他的自己的帐户)
- 不允许用户查看此页面,
AllowAnyRolesIfNoneSpecified = false(例如,您有一个不存在的页面的控制器方法,但您需要包含该方法,因为您的控制器继承自具有此方法的基类)
这是一个示例属性声明:
[AccountAuthorize(UserRole.Admin, MustBeInRoleAndNotPageOwner = true)]
public override ActionResult DeleteConfirmed(int id)
{
...
}
(这意味着管理员可以删除除自己以外的任何帐户。)