Недавно я столкнулся с точно такой же проблемой. В итоге я выбрал собственный ActionFilter, который наследуется от AuthorizeAttribute
.
Он в основном имеет ту же функциональность, что и Authorize
(проверяет, принадлежит ли пользователь хотя бы к одной из перечисленных ролей), но также добавляет возможность проверки, владеет ли пользователь конкретными данными.
Вот код, который вы можете использовать в качестве примера. Если что-то не понятно, пожалуйста, прокомментируйте, и я постараюсь объяснить.
[ Редактировать - Основываясь на предложении Райана, я сделал 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);
}
}
Пара замечаний:
Чтобы избежать "волшебных строк", я использовал массив значений enum UserRole
вместо одной строки. Кроме того, я построил его для нескольких сценариев, с которыми я столкнулся:
- Пользователь должен быть в роли или владельцем страницы / данных (например, администратор может редактировать любые данные, а любой может редактировать свои собственные данные)
- Пользователь должен быть в роли и владельцем страницы / данных (например, пользователь может редактировать только свою собственную страницу / данные - обычно используется без каких-либо ограничений роли)
- Пользователь должен быть в роли, а не - владельцем страницы / данных (например, администратор может редактировать любую страницу / данные, кроме своей, например, чтобы администратор не мог удалить свою собственную). счет)
- Пользователю не разрешено просматривать эту страницу,
AllowAnyRolesIfNoneSpecified = false
(например, у вас есть метод контроллера для страницы, которая не существует, но вам нужно включить метод, потому что ваш контроллер наследует от базового класса, который имеет этот метод )
Вот пример объявления атрибута:
[AccountAuthorize(UserRole.Admin, MustBeInRoleAndNotPageOwner = true)]
public override ActionResult DeleteConfirmed(int id)
{
...
}
(Это означает, что администратор может удалить любую учетную запись, кроме своей.)