Je suis dans le même bateau que vous, j'ai toujours détesté le RoleProviders. Ouais, ils sont parfaits si vous voulez faire les choses en place et en cours d'exécution pour un petit site, mais ils ne sont pas très réalistes. L'inconvénient majeur j'ai toujours trouvé, c'est qu'ils vous lient directement à ASP.NET.
La façon dont je suis allé pour un projet récent a été la définition d'un couple d'interfaces qui font partie de la couche de service (NOTE: j'ai simplifié de ces tout à fait un peu - mais vous pouvez facilement les ajouter à eux):
public interface IAuthenticationService
{
bool Login(string username, string password);
void Logout(User user);
}
public interface IAuthorizationService
{
bool Authorize(User user, Roles requiredRoles);
}
Alors à vos utilisateurs pourraient avoir un Roles
enum:
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; }
}
Pour votre IAuthenticationService
, vous pourriez avoir une base de mise en œuvre qui ne standard de vérification de mot de passe et puis vous pourriez avoir un FormsAuthenticationService
qui ne un peu plus comme la fixation du cookie etc. Pour votre AuthorizationService
, vous auriez besoin de quelque chose comme ceci:
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;
}
}
}
En plus de ces services de base, vous pouvez facilement ajouter des services de réinitialiser les mots de passe etc.
Puisque vous êtes en utilisant MVC, vous pourriez faire d'autorisation au niveau de l'action à l'aide d'un 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;
}
}
}
Vous pouvez ensuite décorer vos actions de contrôleur:
[RequirePermission(Roles.Accounting)]
public ViewResult Index()
{
// ...
}
L'avantage de cette approche est que vous pouvez également utiliser l'injection de dépendance et un conteneur IoC pour le fil des choses. Aussi, vous pouvez l'utiliser dans de multiples applications (et pas seulement votre ASP.NET l'un). Vous utilisez votre ORM pour définir le schéma appropriées.
Si vous avez besoin de plus de détails autour de l' FormsAuthorization/Authentication
services ou où aller à partir d'ici, faites le moi savoir.
EDIT: Pour ajouter "l'ajustement de la sécurité", vous pourriez le faire avec un HtmlHelper. C'est probablement ce qui a besoin d'un peu plus... mais vous voyez l'idée.
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);
}
Et puis à l'intérieur de votre point de vue (à l'aide de la syntaxe Razor ici):
@if(Html.SecurityTrim(Roles.Accounting))
{
<span>Only for accounting</span>
}
EDIT: L' UserSession
ressemblerait à quelque chose comme ceci:
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.
}
}
De cette façon, nous n'avons pas exposer le hachage de mot de passe et tous les autres détails à l'intérieur de la session de l'utilisateur actuel, car ils sont vraiment pas nécessaires pour la session de l'utilisateur durée de vie.