web-dev-qa-db-fra.com

Pourquoi la mise en cache des jetons d'accès est considérée comme mauvaise dans oauth2?

Je suis cet article pour révoquer l'accès utilisateur:

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

Maintenant, pensez après avoir validé l'utilisateur que j'ai émis un accès avec une durée de vie de 30 minutes comme indiqué dans l'article ci-dessus et avec le jeton d'actualisation à 1 jour, mais que faire si l'administrateur supprime cet utilisateur dans 10 minutes avec 20 minutes restantes alors maintenant dans ce cas je dois révoquer l'accès à cet utilisateur.

Pour ce faire, je dois supprimer cette entrée utilisateur de la table de jetons d'actualisation pour interdire toute demande de jeton d'accès supplémentaire, mais le délai d'expiration d'accès étant toujours de 20 minutes, l'utilisateur peut donc accéder à une ressource protégée qui est complètement fausse.

Je pensais donc mettre en œuvre un mécanisme de mise en cache pour mettre en cache le jeton d'accès sur le serveur et également enregistrer dans la base de données. Ainsi, lorsque cet utilisateur est révoqué, je peux simplement supprimer cette entrée d'utilisateur du cache et de la base de données pour empêcher cet accès utilisateur d'accéder aux ressources protégées.

Mais ces 2 réponses ci-dessous disent que ce n'est pas ainsi que oauth2 a été conçu: 

Révoquer le jeton d'accès de OAuthBearerAuthentication

OAuth2 - complexité inutile avec jeton d'actualisation

Donc ma question est:

1) Pourquoi la mise en cache du jeton d'accès n'est pas considérée comme meilleure que le mécanisme d'actualisation du jeton et également une mauvaise approche?

Ma deuxième question est basée sur la réponse ci-dessous donnée par @Hans Z. dans laquelle il dit que:

Cela impliquerait nécessairement que le serveur de ressources (RS) consulte le Authorization Server (AS), qui représente une surcharge considérable.

2) En cas de révocation d'accès pour un utilisateur, pourquoi RS consulterait-il AS, car AS sert uniquement à authentifier l'utilisateur et à générer un jeton d'accès conformément à cet article Article ?

3) Dans l'article, il n'y a que 2 projets:

  • Authentication.api - Authentification de l'utilisateur et génération de jeton d'accès
  • Serveur de ressources - validation de l'accès à l'aide de l'attribut [Authorize]

    Dans le cas ci-dessus, quel est le serveur d'autorisation alors? 

Mise à jour: J'ai décidé d'utiliser le jeton d'actualisation pour révoquer l'accès utilisateur au cas où l'utilisateur serait supprimé et lorsque l'utilisateur se déconnectera, j'actualiserai le jeton de la table d'actualisation des jetons car vous souhaitez que l'utilisateur se déconnecte immédiatement dès que l'utilisateur se déconnecte. clique sur la déconnexion.

Mais ici, le problème est que j’ai 250 rôles associés à un utilisateur, donc si je mets des rôles dans accesstoken, la taille d’accèsstoken sera tellement énorme et nous ne pouvons pas passer un accès aussi énorme depuis header, mais je ne peux pas interroger les rôles pour valider l’accès utilisateur points de terminaison à chaque appel de ce point de terminaison.

C'est donc un autre problème auquel je suis confronté.

Il semble y avoir 2 questions différentes ici: sur le jeton d'accès et sur la grande liste de rôles.

Jeton d'accès

OAuth2 a été conçu pour pouvoir supporter une charge élevée, ce qui nécessite certains compromis. C'est notamment la raison pour laquelle OAuth2 sépare explicitement les rôles "Serveur de ressources" et "Serveur d'autorisations", d'une part, et "Jeton d'accès" et "Jeton d'actualisation", d'autre part. Si, pour chaque demande, vous devez vérifier l'autorisation de l'utilisateur, cela signifie que votre serveur d'autorisations doit pouvoir traiter toutes les demandes de votre système. Cela n'est pas faisable pour les systèmes à forte charge. 

OAuth2 vous permet d'effectuer les compromis suivants entre performances et sécurité: Authorization Server génère un jeton d'accès pouvant être vérifié par le serveur de ressources sans accéder à Authorization Server (du tout ou du moins). pas plus d’une fois pour une vie de Authorization Server). C'est effectivement la mise en cache des informations d'autorisation. Vous pouvez ainsi réduire considérablement la charge sur votre serveur d'autorisations. L’inconvénient est toujours le même que pour la mise en cache: les informations d’autorisation risquent de s’arrêter. En faisant varier la durée de vie des jetons d'accès, vous pouvez ajuster les performances par rapport à l'équilibre de sécurité. 

Cette approche peut également vous aider si vous utilisez une architecture de micro-services où chaque service dispose de son propre stockage et ne se connecte pas.

Néanmoins, si votre charge de travail est faible et que vous n'avez qu'un seul serveur de ressources plutôt que des tonnes de services différents utilisant des technologies différentes, rien ne vous empêche de valider complètement chaque demande. C'est à dire. oui, vous pouvez stocker le jeton d'accès dans la base de données, le vérifier sur chaque accès au serveur de ressources et supprimer tous les jetons d'accès lorsque l'utilisateur est supprimé, etc. .

Grande liste de rôles

AFAIU OAuth2 ne fournit pas de fonctionnalité explicite pour les rôles d'utilisateur. Il existe une fonctionnalité "Étendues" qui pourrait également être utilisée pour les rôles et son implémentation typique produira une chaîne trop longue pour 250 rôles. Néanmoins, OAuth2 ne spécifie pas explicitement de format particulier pour le jeton d'accès. Vous pouvez donc créer un jeton personnalisé contenant les informations sur les rôles sous forme de masque binaire. En utilisant l’encodage en base 64, vous pouvez obtenir 6 rôles dans un seul caractère (64 = 2 ^ 6). Ainsi, 250 à 300 rôles seront gérables entre 40 et 50 caractères.

JWT

Comme vous aurez probablement besoin de jetons personnalisés de toute façon, vous pourriez être intéressé par les JSON Web Tokens aka JWT. En bref, JWT vous permet de spécifier une charge supplémentaire personnalisée (revendications privées) et de placer vos rôles en masque de masque. 

Vous pouvez en fait utiliser JWT seul sans éléments OAuth2 complets si vous n'avez pas réellement besoin des fonctionnalités avancées de OAuth2 (telles que les portées). Bien que les jetons JWT soient supposés être validés uniquement par leur contenu, vous pouvez néanmoins les stocker dans votre base de données locale et effectuer une validation supplémentaire par rapport à la base de données (comme vous le feriez avec un jeton d'actualisation d'accès).


Mise à jour le 1er décembre 2017

Si vous souhaitez utiliser l'infrastructure OWIN OWIN, vous pouvez personnaliser le format de jeton en fournissant un formateur personnalisé via AccessTokenFormat dans OAuthBearerAuthenticationOptions et OAuthAuthorizationServerOptions . Vous pouvez également remplacer RefreshTokenFormat.

Voici un schéma qui montre comment "compresser" les revendications de rôles dans un seul masque binaire:

  1. Définissez votre énumération CustomRoles qui répertorie tous les rôles que vous avez.
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
  1. Créez des méthodes EncodeRoles et DecodeRoles pour convertir le format IEnumerable<string> pour les rôles et le masque binaire codé en base64 en fonction de CustomRoles défini ci-dessus, tels que:
    public static string EncodeRoles(IEnumerable<string> roles)
    {
        byte[] bitMask = new byte[(int)CustomRoles.MaxRole];
        foreach (var role in roles)
        {
            CustomRoles roleIndex = (CustomRoles)Enum.Parse(typeof(CustomRoles), role);
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            bitMask[byteIndex] |= (byte)(1 << bitIndex);
        }
        return Convert.ToBase64String(bitMask);
    }

    public static IEnumerable<string> DecodeRoles(string encoded)
    {
        byte[] bitMask = Convert.FromBase64String(encoded);

        var values = Enum.GetValues(typeof(CustomRoles)).Cast<CustomRoles>().Where(r => r != CustomRoles.MaxRole);

        var roles = new List<string>();
        foreach (var roleIndex in values)
        {
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            if ((byteIndex < bitMask.Length) && (0 != (bitMask[byteIndex] & (1 << bitIndex))))
            {
                roles.Add(Enum.GetName(typeof(CustomRoles), roleIndex));
            }
        }

        return roles;
    }
  1. Utilisez ces méthodes dans une implémentation personnalisée de SecureDataFormat<AuthenticationTicket>. Par souci de simplicité, dans cette esquisse, je délègue l'essentiel du travail aux composants standard OWIN et je viens d'implémenter ma CustomTicketSerializer qui crée une autre AuthenticationTicket et utilise la norme DataSerializers.Ticket. Ce n'est évidemment pas le moyen le plus efficace, mais cela montre ce que vous pouvez faire:
public class CustomTicketSerializer : IDataSerializer<AuthenticationTicket>
{

    public const string RoleBitMaskType = "RoleBitMask";
    private readonly IDataSerializer<AuthenticationTicket> _standardSerializers = DataSerializers.Ticket;

    public static SecureDataFormat<AuthenticationTicket> CreateCustomTicketFormat(IAppBuilder app)
    {
        var tokenProtector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
        var customTokenFormat = new SecureDataFormat<AuthenticationTicket>(new CustomTicketSerializer(), tokenProtector, TextEncodings.Base64Url);
        return customTokenFormat;
    }

    public byte[] Serialize(AuthenticationTicket ticket)
    {
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != identity.RoleClaimType);
        var roleClaims = identity.Claims.Where(c => c.Type == identity.RoleClaimType);
        var encodedRoleClaim = new Claim(RoleBitMaskType, EncodeRoles(roleClaims.Select(rc => rc.Value)));
        var modifiedClaims = otherClaims.Concat(new Claim[] { encodedRoleClaim });
        ClaimsIdentity modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        var modifiedTicket = new AuthenticationTicket(modifiedIdentity, ticket.Properties);
        return _standardSerializers.Serialize(modifiedTicket);
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        var ticket = _standardSerializers.Deserialize(data);
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != RoleBitMaskType);
        var encodedRoleClaim = identity.Claims.SingleOrDefault(c => c.Type == RoleBitMaskType);
        if (encodedRoleClaim == null)
            return ticket;

        var roleClaims = DecodeRoles(encodedRoleClaim.Value).Select(r => new Claim(identity.RoleClaimType, r));
        var modifiedClaims = otherClaims.Concat(roleClaims);
        var modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        return new AuthenticationTicket(modifiedIdentity, ticket.Properties);
    }
}
  1. Dans votre Startup.cs, configurez OWIN pour utiliser votre format personnalisé, tel que:
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
  1. Dans votre OAuthAuthorizationServerProvider, ajoutez ClaimTypes.Role à la ClaimsIdentity pour chaque rôle attribué à l'utilisateur.

  2. Dans votre contrôleur, utilisez la variable AuthorizeAttribute standard telle que

    [Authorize(Roles = "Role1")]
    [Route("")]
    public IHttpActionResult Get()
    

Pour plus de commodité et de sécurité, vous pouvez sous-classe AuthorizeAttribute class pour accepter CustomRoles enum au lieu de string en tant que configuration de rôle.

9
SergGr

Le principal avantage de la méthode d'actualisation des jetons est de réduire le nombre de requêtes aux bases de données. Le jeton d'accès contient les revendications et est signé, ce qui permet de sécuriser le jeton sans interroger la base de données.

Le jeton d'accès en cache fonctionnera, mais vous devrez alors interroger le cache à chaque demande.

C'est un compromis que vous devez choisir entre, n minutes de retard dans les changements d'autorisation d'accès par rapport au nombre de requêtes pour vérifier la validité du jeton d'accès.

Avec une complexité accrue, vous pouvez presque atteindre les deux. Dans ce cas, vous devrez stocker le cache dans la RAM du serveur et ne stocker que les jetons révoqués pour que la liste reste réduite. La complexité vient lorsque vous avez plusieurs instances de serveurs, vous devrez donc synchroniser ce cache de jetons révoqués entre vos RS et AS.

Fondamentalement, lorsque le jeton d'accès est révoqué, le SA devra informer tous les RS pour ajouter ce jeton d'accès au cache de jeton révoqué.

Chaque fois qu'il y a une demande de ressource, RS vérifiera si le jeton est révoqué ou non, à moins que les serveurs RS ne soient révoqués de la ressource. De cette façon, le temps système est présent sur chaque demande, mais il est fortement réduit car le cache est dans la mémoire et le nombre de jetons révoqués sera très inférieur au nombre de jetons valides.

0
Bhoju

J'espère que j'ai bien compris vos questions et que je peux apporter quelques réponses:

1) Vous pouvez l’encaisser si vous avez développé votre AS pour exiger de le valider à chaque fois que l’utilisateur se connecte.

2) Je pense que @Hans Z. signifie révoquer l'utilisateur par l'AS. Lorsque RS révoque l'utilisateur, cela ne change rien au fait qu'il s'agisse toujours de ceux identifiés par AS. Mais lorsque AS révoque l'utilisateur, il empêche l'utilisation de son identité.

3) L'article suppose probablement que l'autorisation est faite par le RS, AS est seul responsable de vous dire qui sont les utilisateurs, et le RS doit décider de l'autorisation en fonction de cela.

0
Majid ALSarra