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