Estoy en el mismo barco que usted - Siempre he odiado a los proveedores de Role. Sí, son geniales si quieres poner las cosas en marcha para un pequeño sitio web , pero no son muy realistas. La desventaja principal que siempre he encontrado es que te atan directamente a ASP.NET.
La forma en que fui para un proyecto reciente fue definir un par de interfaces que son parte de la capa de servicio (NOTA: Me simplificado éstos un poco - pero aquí se puede añadir a ellos):
public interface IAuthenticationService
{
bool Login(string username, string password);
void Logout(User user);
}
public interface IAuthorizationService
{
bool Authorize(User user, Roles requiredRoles);
}
a continuación, sus usuarios podrían tener un Roles
enumeración:
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; }
}
Para su IAuthenticationService
, usted podría tener una aplicación de base que hace la comprobación de contraseñas estándar y entonces se podría tener un FormsAuthenticationService
que hace un poco más como conjunto ting la cookie, etc. Para su AuthorizationService
, que había necesidad de algo como esto:
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;
}
}
}
Además de estos servicios básicos, que fácilmente podría añadir servicios para restablecer las contraseñas etc.
Dado que está utilizando MVC, que podría hacer la autorización en el nivel de acción utilizando 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;
}
}
}
que luego se puede adornar en sus acciones del controlador:
[RequirePermission(Roles.Accounting)]
public ViewResult Index()
{
// ...
}
La ventaja de este enfoque es que también puede usar la inyección de dependencia y un contenedor IoC para conectar las cosas. Además, puede usarlo en múltiples aplicaciones (no solo en ASP.NET).Utilizaría su ORM para definir el esquema apropiado.
Si necesita más detalles sobre los servicios de FormsAuthorization/Authentication
o dónde ir desde aquí, hágamelo saber.
EDITAR: Para agregar "recorte de seguridad", puede hacerlo con un HtmlHelper. Esto probablemente necesite un poco más ... pero entiendes la idea.
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);
}
Y luego dentro de la vista (utilizando la sintaxis Razor aquí):
@if(Html.SecurityTrim(Roles.Accounting))
{
<span>Only for accounting</span>
}
EDIT: El UserSession
sería algo como esto:
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 esta manera, no lo hacemos exponer el hash de la contraseña y todos los demás detalles dentro de la sesión del usuario actual, ya que son realmente no necesarios para la sesión del usuario toda la vida.
estoy ... no muy seguro de lo que "UnitOfWork" tiene que ver con los derechos de acceso de usuarios (roles). ¿No está eso más relacionado con las transacciones que con la autorización? –
@Matti Virkkunen - Es cierto, olvídate de esa parte :) – ebb
¿Podrías explicar lo que quieres decir con "más manejable/flexible"? Actualmente parece que ni siquiera estás seguro de lo que quieres. –