Ответ 1
Я пережил почти такой же сценарий за последние пару недель, чтобы это могло помочь кому-то еще в одной лодке. Мой сценарий - это приложение MVC4 в интрасети компании с пользователями, хранящимися в Active Directory. Это позволяет для проверки подлинности Windows давать единый вход, поэтому нет необходимости в проверке форм. Роли хранятся в базе данных Oracle. У меня три роли:
- Readonly: все пользователи должны быть участниками этого доступа для доступа к приложению
- Пользователь: создать новые рекорды
- Администратор: редактирование и удаление записей
Я решил использовать app.net роль провайдера api для создания собственного AccountRoleProvider. Пока мне нужно использовать только 2 метода: GetRolesForUser и IsUserInRole:
public class AccountRoleProvider : RoleProvider // System.Web.Security.RoleProvider
{
private readonly IAccountRepository _accountRepository;
public AccountRoleProvider(IAccountRepository accountRepository)
{
this._accountRepository = accountRepository;
}
public AccountRoleProvider() : this (new AccountRepository())
{}
public override string[] GetRolesForUser(string user521)
{
var userRoles = this._accountRepository.GetRoles(user521).ToArray();
return userRoles;
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = this.GetRolesForUser(username);
return Utils.IndexOfString(userRoles, roleName) >= 0;
}
}
Я обновил web.config, чтобы использовать роль поставщика ролей:
<authentication mode="Windows" />
<roleManager enabled="true" defaultProvider="AccountRoleProvider">
<providers>
<clear/>
<add name="AccountRoleProvider"
type="MyApp.Infrastructure.AccountRoleProvider" />
</providers>
</roleManager>
Затем я создал 2 пользовательских атрибута из AuthorizeAttribute, ReadOnlyAuthorize и CustomAuthorize.
ReadonlyAuthorize:
public class ReadonlyAuthorize : AuthorizeAttribute
{
private IAccountRepository _accountRepository;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
this._accountRepository = new AccountRepository();
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Get roles for current user
var roles = this._accountRepository.GetRoles(user.Identity.Name);
if (!roles.Contains("readonly"))
{
return false;
}
return base.AuthorizeCore(httpContext);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated && filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
CustomAuthorize:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string RedirectActionName { get; set; }
public string RedirectControllerName { get; set; }
private IAccountRepository _accountRepository;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
this._accountRepository = new AccountRepository();
var accessAllowed = false;
// Get the roles passed in with the (Roles = "...") on the attribute
var allowedRoles = this.Roles.Split(',');
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Get roles for current user
var roles = this._accountRepository.GetRoles(user.Identity.Name);
foreach (var allowedRole in allowedRoles)
{
if (roles.Contains(allowedRole))
{
accessAllowed = true;
}
}
if (!accessAllowed)
{
return false;
}
return base.AuthorizeCore(httpContext);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated && filterContext.Result is HttpUnauthorizedResult)
{
var values = new RouteValueDictionary(new
{
action = this.RedirectActionName == string.Empty ? "AccessDenied" : this.RedirectActionName,
controller = this.RedirectControllerName == string.Empty ? "Home" : this.RedirectControllerName
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
}
Причиной для двух разных атрибутов является то, что я использую один для роли Readonly, для которого все пользователи должны быть членами, чтобы получить доступ к приложению. Я могу добавить это в методе RegisterGlobalFilters в Global.asax, что означает, что он автоматически применяется к каждому контроллеру:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ReadonlyAuthorize());
}
Затем в CustomAuthorize я могу сделать более подробный подход и указать роли, которые я хочу, и применить к контроллеру или отдельному действию, например. ниже Я могу ограничить доступ к методу Delete для пользователей в роли администратора:
[AccessDeniedAuthorize(RedirectActionName = "AccessDenied", RedirectControllerName = "Home", Roles = "Admin")]
public ActionResult Delete(int id = 0)
{
var batch = myDBContext.Batches.Find(id);
if (batch == null)
{
return HttpNotFound();
}
return View(batch);
}
Есть дополнительные шаги, которые мне нужно предпринять, например, обновление объекта User с ролями, к которым принадлежит текущий пользователь. Это будет извлекать роли для пользователя один раз, а не каждый раз в моих пользовательских атрибутах, а также использовать User.IsInRole. Что-то вроде этого должно быть возможно в Application_AuthenticateRequest в Gloal.asax:
var roles = "get roles for this user from respository";
if (Context.User != null)
Context.User = new GenericPrincipal(Context.User.Identity, roles);