MVC Personnalisée d'Authentification, d'Autorisation et de Rôles mise en Œuvre

Ours avec moi comme je l'fournissent les détails de la question...

J'ai un MVC site, à l'aide de FormsAuthentication et personnalisé classes de service pour l'Authentification, l'Autorisation, Rôles/Adhésion, etc.

Authentification

Il y a trois façons de s'inscrire: (1) e-Mail + Alias, (2) OpenID, et (3) nom d'utilisateur + Mot de passe. Tous les trois se à l'utilisateur une authentification par cookie et démarrer une session. Les deux premiers sont utilisés par les visiteurs (session) et le troisième pour les auteurs/admin db comptes.

public class BaseFormsAuthenticationService : IAuthenticationService
{
//Disperse auth cookie and store user session info.
public virtual void SignIn(UserBase user, bool persistentCookie)
{
var vmUser = new UserSessionInfoViewModel { Email = user.Email, Name = user.Name, Url = user.Url, Gravatar = user.Gravatar };
if(user.GetType() == typeof(User)) {
//roles go into view model as string not enum, see Roles enum below.
var rolesInt = ((User)user).Roles;
var rolesEnum = (Roles)rolesInt;
var rolesString = rolesEnum.ToString();
var rolesStringList = rolesString.Split(',').Select(role => role.Trim()).ToList();
vmUser.Roles = rolesStringList;
}
//i was serializing the user data and stuffing it in the auth cookie
//but I'm simply going to use the Session[] items collection now, so 
//just ignore this variable and its inclusion in the cookie below.
var userData = "";
var ticket = new FormsAuthenticationTicket(1, user.Email, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(30), false, userData, FormsAuthentication.FormsCookiePath);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { HttpOnly = true };
HttpContext.Current.Response.Cookies.Add(authCookie);
HttpContext.Current.Session["user"] = vmUser;
}
}

Rôles

Un simple drapeaux enum pour les autorisations:

[Flags]
public enum Roles
{
Guest = 0,
Editor = 1,
Author = 2,
Administrator = 4
}

Enum extension à l'aide d'énumérer drapeau énumérations (wow!).

public static class EnumExtensions
{
private static void IsEnumWithFlags<T>()
{
if (!typeof(T).IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof (T).FullName));
if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
}
public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
{
IsEnumWithFlags<T>();
return from flag in Enum.GetValues(typeof(T)).Cast<T>() let lValue = Convert.ToInt64(value) let lFlag = Convert.ToInt64(flag) where (lValue & lFlag) != 0 select flag;
}
}

Autorisation

Service offre des méthodes pour la vérification d'un utilisateur authentifié rôles.

public class AuthorizationService : IAuthorizationService
{
//Convert role strings into a Roles enum flags using the additive "|" (OR) operand.
public Roles AggregateRoles(IEnumerable<string> roles)
{
return roles.Aggregate(Roles.Guest, (current, role) => current | (Roles)Enum.Parse(typeof(Roles), role));
}
//Checks if a user's roles contains Administrator role.
public bool IsAdministrator(Roles userRoles)
{
return userRoles.HasFlag(Roles.Administrator);
}
//Checks if user has ANY of the allowed role flags.
public bool IsUserInAnyRoles(Roles userRoles, Roles allowedRoles)
{
var flags = allowedRoles.GetFlags();
return flags.Any(flag => userRoles.HasFlag(flag));
}
//Checks if user has ALL required role flags.
public bool IsUserInAllRoles(Roles userRoles, Roles requiredRoles)
{
return ((userRoles & requiredRoles) == requiredRoles);
}
//Validate authorization
public bool IsAuthorized(UserSessionInfoViewModel user, Roles roles)
{
//convert comma delimited roles to enum flags, and check privileges.
var userRoles = AggregateRoles(user.Roles);
return IsAdministrator(userRoles) || IsUserInAnyRoles(userRoles, roles);
}
}

J'ai choisi de l'utiliser dans mon contrôleurs via un attribut:

public class AuthorizationFilter : IAuthorizationFilter
{
private readonly IAuthorizationService _authorizationService;
private readonly Roles _authorizedRoles;
///<summary>
///Constructor
///</summary>
///<remarks>The AuthorizedRolesAttribute is used on actions and designates the 
///required roles. Using dependency injection we inject the service, as well 
///as the attribute's constructor argument (Roles).</remarks>
public AuthorizationFilter(IAuthorizationService authorizationService, Roles authorizedRoles)
{
_authorizationService = authorizationService;
_authorizedRoles = authorizedRoles;
}
///<summary>
///Uses injected authorization service to determine if the session user 
///has necessary role privileges.
///</summary>
///<remarks>As authorization code runs at the action level, after the 
///caching module, our authorization code is hooked into the caching 
///mechanics, to ensure unauthorized users are not served up a 
///prior-authorized page. 
///Note: Special thanks to TheCloudlessSky on StackOverflow.
///</remarks>
public void OnAuthorization(AuthorizationContext filterContext)
{
//User must be authenticated and Session not be null
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || filterContext.HttpContext.Session == null)
HandleUnauthorizedRequest(filterContext);
else {
//if authorized, handle cache validation
if (_authorizationService.IsAuthorized((UserSessionInfoViewModel)filterContext.HttpContext.Session["user"], _authorizedRoles)) {
var cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0));
cache.AddValidationCallback((HttpContext context, object o, ref HttpValidationStatus status) => AuthorizeCache(context), null);
}
else
HandleUnauthorizedRequest(filterContext);             
}
}

Je décore des Actions dans mon Contrôleurs avec cet attribut, et à l'instar de Microsoft [Authorize] pas params signifie que laisser à quelqu'un authentifié (pour moi, c'est Enum = 0, pas de rôles).

Que sur encapsule les informations de fond (ouf)... et d'écrire tout ce que j'ai répondu à ma première question. À ce point, je suis curieux au sujet de la pertinence de mon installation:

  1. Je dois manuellement de s'accrocher à l'auth cookie et remplir le FormsIdentity principal pour la HttpContext ou devrait être automatique?
  2. Des problèmes avec la vérification de l'authentification au sein de l'attribut/filtre OnAuthorization()?
  3. Quels sont les compromis en utilisant Session[] pour stocker mon point de vue, le modèle vs sérialisation dans le auth cookie?
  4. Cette solution semble suivre la " séparation des préoccupations des idéaux assez bien? (Bonus que c'est plus l'opinion orientée question)

OriginalL'auteur one.beat.consumer | 2011-12-19