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:
- Je dois manuellement de s'accrocher à l'auth cookie et remplir le FormsIdentity principal pour la
HttpContext
ou devrait être automatique? - Des problèmes avec la vérification de l'authentification au sein de l'attribut/filtre
OnAuthorization()
? - Quels sont les compromis en utilisant
Session[]
pour stocker mon point de vue, le modèle vs sérialisation dans le auth cookie? - 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
Vous devez vous connecter pour publier un commentaire.
De la croix-post de mon CodeReview répondre:
Je vais prendre un coup de couteau à répondre à vos questions et vous fournir quelques suggestions:
Si vous avez FormsAuthentication configuré dans
web.config
, il tirera automatiquement les cookies, pour vous, de sorte que vous ne devriez pas avoir à n'importe quel manuel de la population de la FormsIdentity. C'est assez facile à tester en tout cas.Vous voulez probablement pour remplacer les deux
AuthorizeCore
etOnAuthorization
effective de l'autorisation de l'attribut. LeAuthorizeCore
méthode retourne un booléen et est utilisé pour déterminer si l'utilisateur a accès à une ressource donnée. LeOnAuthorization
ne retourne pas et est généralement utilisé pour déclencher d'autres choses basées sur le statut d'authentification.Je pense que la session-vs-cookie question est en grande partie la préférence, mais je vous conseille d'aller à la séance pour quelques raisons. La principale raison est que le cookie est transmis avec chaque demande, et tout droit maintenant, vous n'avez que peu de données, comme le temps passe, qui sait ce que vous aurez des trucs là. Ajouter le chiffrement frais généraux et il pourrait arriver assez grand pour ralentir les demandes. De le stocker dans la session met également la propriété des données dans vos mains (plutôt que de le mettre dans les mains et comptons sur vous pour les décrypter et de les utiliser). Une suggestion que je ferais est d'habillage que l'accès à la session en statique
UserContext
classe, semblable àHttpContext
, de sorte que vous pouvez simplement faire un appel commeUserContext.Current.UserData
. Voir ci-dessous pour un exemple de code.Je ne peux pas vraiment dire si c'est une bonne séparation des préoccupations, mais il semble comme une bonne solution pour moi. Ce n'est pas, contrairement à d'autres MVC authentification des approches que j'ai vu. Je suis l'aide de quelque chose de très similaire dans mes applications, en fait.
Une dernière question, pourquoi l'avez-vous construire et de créer le cookie FormsAuthentication manuellement au lieu d'utiliser
FormsAuthentication.SetAuthCookie
? Juste curieux.Code d'exemple de contexte statique de la classe
Dans le passé, j'avais mis des propriétés directement sur la
UserContext
classe pour l'accès à l'utilisateur les données dont j'avais besoin, mais comme je l'ai déjà utilisé pour d'autres, des projets plus complexes, j'ai décidé de passer à unSiteUser
classe:OriginalL'auteur
Alors que je pense que vous faites un excellent travail avec cela, je me demande pourquoi vous recréer la roue. Depuis que microsoft fournit un système de celle-ci, appelée la composition et le Rôle des Fournisseurs. Pourquoi ne pas simplement écrire une coutume composition et le rôle de fournisseur, alors vous n'avez pas à créer votre propre authization attribut et/ou les filtres, et peut tout simplement utiliser la fonction intégrée de.
Flags
enums et de liaison de l'Attribut/Filtre trucs pour DI fait obtenir rapidement complexes. L'extrémité arrière est mongo, qui je suis, certes nouvelle, et ma solution semble me donner un peu plus de liberté avec les db schéma de structure. qui fait sens?c'est pourquoi vous permettrait d'étendre les interfaces du Fournisseur, plutôt que d'utiliser celles qui existent déjà. En ne suivant pas les interfaces existantes, il est absent dehors sur une beaucoup de l'intégré dans l'outillage fait pour les soutenir.
beaucoup de le câblage en MVC attend les classes de base abstraites, pas les interfaces qu'ils (n'est généralement pas le même) mettre en œuvre.
Une raison pour laquelle vous ne pouvez pas être obtenir des réponses est parce que vous êtes en train de faire les choses d'une manière que personne d'autre ne l'est. En d'autres termes, vous allez à contre-courant. Personnellement, je pense que vous vous faites de cette 10x plus fort que vous en avez besoin.
je ne vous dis pas de ne pas poser la question, je suis en expliquant pourquoi vous n'avez pas obtenu de réponses à votre question. Aussi, le système d'Adhésion fonctionne très bien avec Oracle, Raven, Mongo, etc.. vous pourriez avoir à mettre en place votre propre fournisseur. C'est la raison entière Adhésion des fournisseurs existent, pour vous permettre de brancher quoi que backend vous le souhaitez.
OriginalL'auteur
Votre MVC Personnalisée d'Authentification, d'Autorisation et de Rôles mise en Œuvre semble bon. Pour répondre à votre première question, quand vous n'êtes pas à l'aide d'un membershipprovider vous devez remplir les FormsIdentity principal vous-même. Une solution que j'utilise est décrit ici Mon Blog
OriginalL'auteur