web-dev-qa-db-fra.com

Comment puis-je obtenir un rôle d'utilisateurs dans une méthode d'API Web sans recherche dans la table AspNetUsers?

J'ai une procédure stockée qui met à jour le statut. Selon le rôle de l'utilisateur, la procédure stockée possède un code qui peut ou non autoriser le changement d'état. Pour cette raison, je dois passer un nom de rôle dans une procédure stockée. Mon nom de rôle est stocké sur le client dans mon code javascript mais bien sûr j'ai besoin d'un deuxième contrôle sur le serveur. Chaque utilisateur n'a qu'un seul des trois rôles et lorsque je demande une mise à jour de l'état, je peux appeler l'une des trois méthodes en fonction du rôle du client. Voici ce que j'ai essayé.

J'utilise WebApi avec authentification basée sur un jeton porteur et ASP.NET Identity 2.1 et l'application s'exécute toujours dans un navigateur. Mes utilisateurs ont été configurés avec les rôles appropriés.

J'ai mis en place du code pour obtenir l'ID utilisateur, puis je vais dans la table AspNetUserRoles pour obtenir le rôle au début d'une méthode. Cependant, j'ai remarqué que cela prend environ 500 millisecondes pour s'exécuter. Comme alternative, j'envisage ce qui suit:

    [HttpPut]
    [Authorize(Roles = "Admin")]
    [Route("AdminUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
    public async Task<IHttpActionResult> AdminUpdateStatus(int userTestId, int userTestStatusId)
    {
        return await UpdateStatusMethod(userTestId, userTestStatusId, "Admin");
    }

    [HttpPut]
    [Authorize(Roles = "Student")]
    [Route("StudentUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
    public async Task<IHttpActionResult> StudentUpdateStatus(int userTestId, int userTestStatusId)
    {
        return await UpdateStatusMethod(userTestId, userTestStatusId, "Student");
    }

    [HttpPut]
    [Authorize(Roles = "Teacher")]
    [Route("TeacherUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
    public async Task<IHttpActionResult> TeacherUpdateStatus(int userTestId, int userTestStatusId)
    {
        return await UpdateStatusMethod(userTestId, userTestStatusId, "Teacher");
    }

    private async Task<IHttpActionResult> UpdateStatusMethod(int userTestId, int userTestStatusId, string roleName)
    {
        // Call the stored procedure here and pass in the roleName
    }

Est-ce un moyen efficace de le faire ou existe-t-il peut-être un autre moyen plus propre. Ce que je ne sais pas assez, c'est si le front ou le back-end met en cache le rôle des utilisateurs. Je suppose que cela est fait ou qu'il existe un paramètre qui permettra de le faire.

Remarque J'utilise des revendications pour envoyer les informations de rôle à mon client ici:

public static AuthenticationProperties CreateProperties(
            string userName,
            ClaimsIdentity oAuthIdentity,
            string firstName,
            string lastName,
            int organization)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
                {
                    { "userName", userName},
                    { "firstName", firstName},
                    { "lastName", lastName},
                    { "organization", organization.ToString()},
                    { "roles",string.Join(":",oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray())}

                };
            return new AuthenticationProperties(data);
        }

Cependant ma question ici concerne le serveur et comment je peux vérifier dans ma méthode si un utilisateur est dans un certain rôle sans aller à la base de données. Peut-être qu'il y a un moyen de le faire en toute sécurité avec des réclamations, mais je ne sais pas comment le faire.

Toute aide et conseil serait très apprécié.

25
Samantha J T Star

Comme vous l'avez indiqué, vous utilisez des jetons au porteur pour protéger vos points de terminaison. Je pense qu'il y a peu de malentendu avec ce que contient la chaîne magique de ces jetons de porteur. Eh bien, ces jetons contiennent tous les rôles de l'utilisateur pour lequel vous avez émis le jeton, ainsi si vous utilisez le DPAPI de protection des données par défaut dans l'API Web non (jetons JWT), alors ces jetons sont signés et cryptés afin que personne ne puisse altérer les données à l'intérieur du jeton, à moins qu'il ne dispose de la clé mashine pour le serveur Web qui a émis ce jeton, ne vous inquiétez donc pas de la protection des données.

Ma recommandation est de lire les rôles/revendications de l'utilisateur à partir de la base de données, il n'y a pas besoin de ces solutions de contournement et de hacks que vous essayez de faire, tout ce que vous devez faire est de définir les revendications pour les utilisateurs lorsqu'ils se connectent dans la méthode GrantResourceOwnerCredentials Vous pouvez le définir de cette façon en obtenant l'utilisateur puis en lisant les rôles à partir de la base de données et en les définissant comme revendication de type "Rôle"

 var identity = new ClaimsIdentity(context.Options.AuthenticationType);
 identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
 identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
 identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));

N'oubliez pas que cela ne se produit qu'une seule fois lors de la connexion de l'utilisateur, vous recevrez alors un jeton signé et chiffré au porteur qui contient toutes les revendications de cet utilisateur, aucun besoin d'accès DB pour le vérifier.

Ou si vous souhaitez créer l'identité à partir de la base de données, vous pouvez utiliser ce qui suit:

 public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
        // Add custom user claims here
        return userIdentity;
    }

Ensuite, dans GrantResourceOwnerCredentials, procédez comme suit:

ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);

Maintenant, une fois que vous avez envoyé le jeton porteur à un point de terminaison protégé avec l'attribut Authorize tel que [Authorize(Roles = "Teacher")] je peux vous assurer que votre code n'ira pas à la base de données pour faire une requête ouvrez le profileur SQL et vérifiez qu'il lira les revendications du jeton chiffré avec la revendication des rôles et vérifiera si cet utilisateur appartient à l'enseignant et autorisez ou refusez la demande.

J'ai blogué une série détaillée de 5 articles sur Authentification basée sur les jetons avec Serveur d'autorisation et Jetons JWT . Je vous recommande de lire ces articles pour mieux comprendre les jetons au porteur.

34
Taiseer Joudeh

Plutôt que d'avoir 3 méthodes distinctes, vous pouvez simplement vérifier la User.IsInRole("RoleName") disponible dans votre classe de contrôleur. Il utilise la même logique que dans le [Authorise] attribut. par exemple.

public class DataApiController : ApiController
{
    [HttpPut]
    [Route("UpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
    public async Task<IHttpActionResult> UpdateStatus(int userTestId, int userTestStatusId)
    {
        if(User.IsInRole("Admin"))
        {
            //Update method etc....
        }
        //else if(....) else etc
    {
}
10
Excommunicated

Les revendications de rôle par défaut sont stockées dans le cookie de connexion de l'utilisateur, donc [Autoriser (rôles = "Enseignant")] devrait être rapide. Le seul accès à la base de données sera lorsque vous vous connecterez pour la première fois (ou chaque fois que le cookie de connexion est actualisé. Les vérifications d'autorisation sont effectuées par rapport à IClaimsPrincipal créé à partir du cookie de connexion.

Vous devrez peut-être également mettre à jour un autre code pour que cela fonctionne, voir cette question qui est similaire à ce que vous faites: Autoriser avec les rôles

2
Hao Kung