Я в одной лодке с тобой - я всегда ненавидел RoleProviders. Да, они хороши, если вы хотите, чтобы все было готово для небольшого сайта , но они не очень реалистичны. Основным недостатком, который я всегда обнаруживал, является то, что они напрямую связывают вас с ASP.NET.
В моем недавнем проекте я определил пару интерфейсов, являющихся частью уровня обслуживания (ПРИМЕЧАНИЕ. Я немного их упростил, но вы могли бы легко добавить их):
public interface IAuthenticationService
{
bool Login(string username, string password);
void Logout(User user);
}
public interface IAuthorizationService
{
bool Authorize(User user, Roles requiredRoles);
}
Тогда ваши пользователи могут иметь перечисление Roles
:
public enum Roles
{
Accounting = 1,
Scheduling = 2,
Prescriptions = 4
// What ever else you need to define here.
// Notice all powers of 2 so we can OR them to combine role permissions.
}
public class User
{
bool IsAdministrator { get; set; }
Roles Permissions { get; set; }
}
Для вашего IAuthenticationService
у вас может быть базовая реализация, которая выполняет стандартную проверку пароля, а затем у вас может быть FormsAuthenticationService
, который немного больше, например, установка cookie и т. Д. Для вашего AuthorizationService
вы ' мне нужно что-то вроде этого:
public class AuthorizationService : IAuthorizationService
{
public bool Authorize(User userSession, Roles requiredRoles)
{
if (userSession.IsAdministrator)
{
return true;
}
else
{
// Check if the roles enum has the specific role bit set.
return (requiredRoles & user.Roles) == requiredRoles;
}
}
}
Помимо этих базовых служб вы можете легко добавлять службы для сброса паролей и т. Д.
Поскольку вы используете MVC, вы можете выполнить авторизацию на уровне действий, используя ActionFilter
:
public class RequirePermissionFilter : IAuthorizationFilter
{
private readonly IAuthorizationService authorizationService;
private readonly Roles permissions;
public RequirePermissionFilter(IAuthorizationService authorizationService, Roles requiredRoles)
{
this.authorizationService = authorizationService;
this.permissions = requiredRoles;
this.isAdministrator = isAdministrator;
}
private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext)
{
return this.authorizationService ?? new FormsAuthorizationService(httpContext);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var authSvc = this.CreateAuthorizationService(filterContext.HttpContext);
// Get the current user... you could store in session or the HttpContext if you want too. It would be set inside the FormsAuthenticationService.
var userSession = (User)filterContext.HttpContext.Session["CurrentUser"];
var success = authSvc.Authorize(userSession, this.permissions);
if (success)
{
// Since authorization is performed at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether or not a page should be served from the cache.
var cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0));
cache.AddValidationCallback((HttpContext context, object data, ref HttpValidationStatus validationStatus) =>
{
validationStatus = this.OnCacheAuthorization(new HttpContextWrapper(context));
}, null);
}
else
{
this.HandleUnauthorizedRequest(filterContext);
}
}
private void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Ajax requests will return status code 500 because we don't want to return the result of the
// redirect to the login page.
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new HttpStatusCodeResult(500);
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
{
var authSvc = this.CreateAuthorizationService(httpContext);
var userSession = (User)httpContext.Session["CurrentUser"];
var success = authSvc.Authorize(userSession, this.permissions);
if (success)
{
return HttpValidationStatus.Valid;
}
else
{
return HttpValidationStatus.IgnoreThisRequest;
}
}
}
Что вы можете затем украсить на действиях вашего контроллера:
[RequirePermission(Roles.Accounting)]
public ViewResult Index()
{
// ...
}
Преимущество этого подхода заключается в том, что вы также можете использовать внедрение зависимостей и контейнер IoC для соединения. Кроме того, вы можете использовать его в нескольких приложениях (не только в вашем ASP.NET). Вы бы использовали ORM для определения подходящей схемы.
Если вам нужна более подробная информация о FormsAuthorization/Authentication
услугах или о том, куда идти отсюда, сообщите мне.
РЕДАКТИРОВАТЬ: Чтобы добавить «усечение безопасности», вы можете сделать это с помощью HtmlHelper. Это, вероятно, нужно немного больше ... но вы поняли.
public static bool SecurityTrim<TModel>(this HtmlHelper<TModel> source, Roles requiredRoles)
{
var authorizationService = new FormsAuthorizationService();
var user = (User)HttpContext.Current.Session["CurrentUser"];
return authorizationService.Authorize(user, requiredRoles);
}
А затем внутри вашего представления (используя синтаксис Razor здесь):
@if(Html.SecurityTrim(Roles.Accounting))
{
<span>Only for accounting</span>
}
EDIT: UserSession
будет выглядеть примерно так:
public class UserSession
{
public int UserId { get; set; }
public string UserName { get; set; }
public bool IsAdministrator { get; set; }
public Roles GetRoles()
{
// make the call to the database or whatever here.
// or just turn this into a property.
}
}
Таким образом, мы не раскрываем хэш пароля и все другие детали внутри сеанса текущего пользователя, поскольку они действительно не нужны для продолжительности сеанса пользователя.