46 votes

ASP.NET MVC - Alternative à un Rôle de Fournisseur?

J'essaie d'éviter l'utilisation de la Rôle de Fournisseur et le Fournisseur d'appartenances depuis sa trop maladroit à mon avis, et donc je suis en train de faire mon propre "version", qui est moins maladroit et plus facile à gérer/flexible. Maintenant ma question.. est-il une alternative à la Rôle de Fournisseur qui est décent? (Je sais que je peux le faire personnalisé Rôle provier, fournisseur d'appartenances etc.)

De plus gérable/flexible, je veux dire que je suis limité à l'utilisation de l'Rôles statiques de la classe, et ne pas appliquer directement dans ma couche de service, qui interagissent avec le contexte de base de données, au lieu de cela je suis obligé d'utiliser les Rôles statiques de la classe qui dispose de sa propre base de données de contexte etc, aussi les noms de table est terrible..

Merci à l'avance.

87voto

TheCloudlessSky Points 7706

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.

5voto

Hamid Points 921

J'ai mis en place un fournisseur de rôle basé sur @TheCloudlessSky poster ici. Il y a peu de choses que je pensais que je peux ajouter et de partager ce que j'ai fait. D'abord, si vous souhaitez utiliser l' RequirepPermission classe pour votre action filtres comme un attribut, vous devez implémenter ActionFilterAttribute classe pour RequirepPermission classe.

Classes d'Interface IAuthenticationService et IAuthorizationService

public interface IAuthenticationService
{
    void SignIn(string userName, bool createPersistentCookie);
    void SignOut();
}

public interface IAuthorizationService
{
    bool Authorize(UserSession user, string[] requiredRoles);
}

FormsAuthenticationService classe

/// <summary>
/// This class is for Form Authentication
/// </summary>
public class FormsAuthenticationService : IAuthenticationService
{

    public void SignIn(string userName, bool createPersistentCookie)
    {
        if (String.IsNullOrEmpty(userName)) throw new ArgumentException(@"Value cannot be null or empty.", "userName");

        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }

    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

UserSession calss

public class UserSession
{
    public string UserName { get; set; }
    public IEnumerable<string> UserRoles { get; set; }
}

Un autre point est - FormsAuthorizationServicede la classe et comment nous pouvons attribuer à un utilisateur à l' httpContext.Session["CurrentUser"]. Ma Démarche dans cette situation est de créer une nouvelle instance de userSession classe et affecter directement l'utilisateur à partir d' httpContext.User.Identity.Name de la userSession variable comme vous pouvez le voir en FormsAuthorizationService classe.

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)]
public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
    #region Fields

    private readonly IAuthorizationService _authorizationService;
    private readonly string[] _permissions;

    #endregion

    #region Constructors

    public RequirePermissionAttribute(string requiredRoles)
    {
        _permissions = requiredRoles.Trim().Split(',').ToArray();
        _authorizationService = null;
    }

    #endregion

    #region Methods

    private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext)
    {
        return _authorizationService ?? new FormsAuthorizationService(httpContext);
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var authSvc = 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.
        if (filterContext.HttpContext.Session == null) return;
        if (filterContext.HttpContext.Request == null) return;
        var success = false;
        if (filterContext.HttpContext.Session["__Roles"] != null)
        {
            var rolesSession = filterContext.HttpContext.Session["__Roles"];
            var roles = rolesSession.ToString().Trim().Split(',').ToList();
            var userSession = new UserSession
            {
                UserName = filterContext.HttpContext.User.Identity.Name,
                UserRoles = roles
            };
            success = authSvc.Authorize(userSession, _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 = OnCacheAuthorization(new HttpContextWrapper(context));
                                                }, null);
            }
            else
            {
                HandleUnauthorizedRequest(filterContext);
            }
    }

    private static 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();
        }
    }

    private HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
    {
        var authSvc = CreateAuthorizationService(httpContext);
        if (httpContext.Session != null)
        {
            var success = false;
            if (httpContext.Session["__Roles"] != null)
            {
                var rolesSession = httpContext.Session["__Roles"];
                var roles = rolesSession.ToString().Trim().Split(',').ToList();
                var userSession = new UserSession
                {
                    UserName = httpContext.User.Identity.Name,
                    UserRoles = roles
                };
                success = authSvc.Authorize(userSession, _permissions);
            }
            return success ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
        }
        return 0;
    }

    #endregion
}

internal class FormsAuthorizationService : IAuthorizationService
{
    private readonly HttpContextBase _httpContext;

    public FormsAuthorizationService(HttpContextBase httpContext)
    {
        _httpContext = httpContext;
    }

    public bool Authorize(UserSession userSession, string[] requiredRoles)
    {
        return userSession.UserRoles.Any(role => requiredRoles.Any(item => item == role));
    }
}

puis dans votre contrôleur, après que l'utilisateur est authentifié, vous pouvez obtenir des rôles de la base de données et assigner les rôles de la session:

var roles = Repository.GetRolesByUserId(Id);
if (ControllerContext.HttpContext.Session != null)
   ControllerContext.HttpContext.Session.Add("__Roles",roles);
FormsService.SignIn(collection.Name, true);

Une fois que l'utilisateur est déconnecté du système, vous pouvez effacer la séance

FormsService.SignOut();
Session.Abandon();
return RedirectToAction("Index", "Account");

L'inconvénient de ce modèle est que, lorsque l'utilisateur est connecté sur le système, si un rôle est attribué à l'utilisateur, l'autorisation ne fonctionne pas sauf si il se déconnecte et se reconnecte dans le système.

Une autre chose est qu'il n'est pas nécessaire d'avoir une catégorie distincte pour les rôles, puisque l'on peut obtenir directement des rôles de base de données et dans des rôles de session dans un contrôleur.

Après vous avez terminé avec la mise en œuvre de tous ces codes, une dernière étape consiste à lier cet attribut à vos méthodes dans votre contrôleur:

[RequirePermission("Admin,DM")]
public ActionResult Create()
{
return View();
}

2voto

ActualAl Points 466

Si vous utilisez le Château de Windsor, l'Injection de Dépendance, vous pouvez injecter des listes de RoleProviders qui peut être utilisé pour établir les droits de l'utilisateur à partir de n'importe quelle source que vous choisissez de mettre en œuvre.

http://ivida.co.uk/2011/05/18/mvc-getting-user-roles-from-multiple-sources-register-and-resolve-arrays-of-dependencis-using-the-fluent-api/

1voto

Matti Virkkunen Points 31633

Vous n'avez pas besoin d'utiliser une classe statique pour des rôles. Par exemple, le SqlRoleProvider vous permet de définir les rôles de chacun dans une base de données.

Bien sûr, si vous voulez récupérer des rôles à partir de votre propre couche de service, il n'est pas difficile de créer votre propre fournisseur de rôle - là ne sont pas vraiment que beaucoup de méthodes à mettre en œuvre.

0voto

Brook Points 3985

Vous pouvez implémenter votre propre adhésion et le rôle des fournisseurs en substituant les interfaces appropriées.

Si vous voulez commencer à partir de zéro, en général, ces types de choses sont mises en œuvre comme un module http personnalisé qui stocke les informations d'identification des utilisateurs soit dans le httpcontext ou de la session. De toute façon vous aurez probablement envie de mettre un cookie avec une sorte de jeton d'authentification.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X