wake-up-neo.com

Warum wird das Cachen von Zugriffstoken in oauth2 als schlecht angesehen?

Ich verfolge diesen Artikel, um den Benutzerzugriff zu widerrufen:

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

Betrachten Sie nun nach der Validierung des Benutzers, dass ich eine Bestätigung mit einer Lebensdauer von 30 Minuten, wie im obigen Artikel gezeigt, und einem Aktualisierungstoken von 1 Tag ausgestellt habe, aber was ist, wenn der Administrator diesen Benutzer in 10 Minuten löscht, wobei in diesem Fall noch 20 Minuten übrig sind? Zugriff auf diesen Benutzer widerrufen.

Um dies zu tun, muss ich diesen Benutzereintrag aus der Aktualisierungstoken-Tabelle entfernen, um eine weitere Anforderung von Zugriffstoken zu verhindern, aber da die Ablaufzeit für Zugriffstoken immer noch 20 Minuten beträgt, kann der Benutzer auf geschützte Ressourcen zugreifen, was völlig falsch ist.

Also überlegte ich, einen Cache-Mechanismus zu implementieren, um das Zugriffstoken auf dem Server zwischenzuspeichern und auch in der Datenbank zu speichern . Wenn dieser Benutzer widerrufen wird, kann ich diesen Benutzereintrag einfach aus dem Cache und der Datenbank entfernen, um zu verhindern, dass dieser Benutzer auf geschützte Ressourcen zugreift.

Die folgenden 2 Antworten sagen jedoch, dass oauth2 nicht so entworfen wurde:

Zugriffstoken von OAuthBearerAuthentication entziehen

OAuth2 - unnötige Komplexität mit Aktualisierungstoken

Meine Frage lautet also:

1) Warum wird das Zwischenspeichern von Zugriffstoken nicht als besser als der Mechanismus zum Aktualisieren von Token und auch als schlechter Ansatz angesehen?

Meine zweite Frage basiert auf der Antwort von @Hans Z. , in der er folgendes sagt:

Dies würde notwendigerweise bedeuten, dass der Resource Server (RS) den Authorization Server (AS) konsultiert, was einen enormen Aufwand bedeutet.

2) Wenn einem Benutzer der Zugriff entzogen wird, warum sollte RS AS konsultieren, da AS nur zur Authentifizierung des Benutzers und zur Generierung eines Zugriffstokens gemäß diesem Artikel dient?

3) In dem Artikel gibt es nur 2 Projekte:

  • Authentication.api - Benutzer authentifizieren und Zugriffstoken generieren
  • Ressourcenserver - Überprüfung des Zugriffs mit Hilfe des Attributs [Authorize]

    In obigem Fall, welcher ist der Autorisierungsserver dann?

Update: Ich habe beschlossen, das Aktualisierungstoken zu verwenden, um den Benutzerzugriff für den Fall zu widerrufen, dass der Benutzer gelöscht wird. Außerdem aktualisiere ich das Token aus der Aktualisierungstokentabelle, da Sie den Benutzer sofort abmelden möchten als Benutzer klickt auf Abmelden.

Aber hier ist das Problem Ich habe 250 Rollen, die mit dem Benutzer verbunden sind. Wenn ich also Rollen in Zugriffsrechte lege, ist die Größe des Zugriffsrechts so groß und wir können kein so großes Zugriffsrecht aus dem Header übergeben , aber ich kann keine Rollen abfragen, um den Benutzer zu validieren Zugriff auf Endpunkte bei jedem Aufruf dieses Endpunkts.

Das ist also ein weiteres Problem, mit dem ich konfrontiert bin.

Es scheint hier zwei verschiedene Fragen zu geben: nach dem Zugriffstoken und nach der großen Liste der Rollen.

Zugangstoken

OAuth2 wurde entwickelt, um hohe Lasten zu bewältigen, und dies erfordert einige Kompromisse. Dies ist insbesondere der Grund, warum OAuth2 die Rollen "Resource Server" und "Authorization Server" einerseits und "Zugriffstoken" und "Aktualisierungstoken" andererseits explizit voneinander trennt. Wenn Sie für jede Anforderung die Benutzerautorisierung überprüfen müssen, bedeutet dies, dass Ihr Autorisierungsserver in der Lage sein sollte, alle Anforderungen in Ihrem System zu bearbeiten. Für Hochlastsysteme ist dies nicht möglich. 

OAuth2 ermöglicht Folgendes: Trade-Off zwischen Leistung und Sicherheit: Autorisierungsserver generiert ein Zugriffstoken, das vom Ressourcenserver ohne Zugriff auf den Autorisierungsserver (entweder überhaupt oder zumindest nicht mehr) überprüft werden kann als einmal für ein Leben von Authorization Server). Dies ist eine effektive Zwischenspeicherung der Berechtigungsinformationen. Auf diese Weise können Sie die Belastung Ihres Autorisierungsservers drastisch reduzieren. Der Nachteil ist wieder derselbe wie beim Caching: Die Berechtigungsinformationen können zum Stillstand kommen. Durch die Änderung der Lebensdauer des Zugriffstokens können Sie die Leistung im Verhältnis zur Sicherheitsbilanz anpassen. 

Dieser Ansatz kann auch hilfreich sein, wenn Sie eine Mikrodienstarchitektur durchführen, bei der jeder Dienst über einen eigenen Speicher verfügt und nicht auf den anderen zugreifen kann.

Wenn Sie jedoch nicht über eine große Auslastung verfügen und nur einen einzelnen Ressourcenserver haben, statt Tonnen von verschiedenen Services, die mit unterschiedlichen Technologien implementiert werden, gibt es nichts, was Sie daran hindert, eine vollständige Validierung für jede Anforderung durchzuführen. Das heißt Ja, Sie können das Zugriffstoken in der Datenbank speichern, bei jedem Zugriff auf den Ressourcenserver überprüfen und alle Zugriffstoken entfernen, wenn der Benutzer gelöscht wird usw. Wenn @Evk dies jedoch bemerkt, ist dies Ihr Szenario - OAuth2 ist eine Überschreitung für Sie .

Große Rollenliste

AFAIU OAuth2 bietet keine explizite Funktion für Benutzerrollen. Es gibt eine "Scope" -Funktion, die möglicherweise auch für Rollen verwendet wird, und ihre typische Implementierung führt zu einer zu langen Zeichenfolge für 250 Rollen. OAuth2 gibt jedoch kein bestimmtes Format für das Zugriffstoken an. Daher können Sie ein benutzerdefiniertes Token erstellen, das Rolleninformationen als Bitmaske enthält. Bei der Base-64-Codierung können Sie 6 Rollen in ein einzelnes Zeichen (64 = 2 ^ 6) umwandeln. So werden 250-300 Rollen 40-50 Zeichen überschaubar sein.

JWT

Da Sie wahrscheinlich sowieso ein benutzerdefiniertes Token benötigen, interessieren Sie sich möglicherweise für die JSON Web Tokens aka JWT. Kurz gesagt, in JWT können Sie benutzerdefinierte zusätzliche Nutzdaten angeben (private Claims) und Ihre Rollen als Bitmaske dort ablegen. 

Sie können JWT auch ganz ohne OAuth2-Sachen verwenden, wenn Sie die erweiterten Funktionen von OAuth2 (z. B. Bereiche) nicht wirklich benötigen. Obwohl JWT-Token nur durch ihren Inhalt validiert werden sollen, können Sie sie dennoch in Ihrer lokalen Datenbank speichern und eine zusätzliche Validierung gegen die DB durchführen (wie Sie es mit dem Zugriffsaktualisierungstoken tun würden).


Update 1. Dezember 2017

Wenn Sie die OWIN OAuth-Infrastruktur verwenden möchten, können Sie das Tokenformat mit dem benutzerdefinierten Formatierungsprogramm über AccessTokenFormat in OAuthBearerAuthenticationOptions und OAuthAuthorizationServerOptions anpassen. Sie können auch RefreshTokenFormat überschreiben.

Hier ist eine Skizze, die zeigt, wie Sie Rollenansprüche in eine einzelne Bitmaske "komprimieren" können:

  1. Definieren Sie Ihre CustomRoles-Enumeration, in der alle Rollen aufgelistet sind, die Sie haben
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
  1. Erstellen Sie die Methoden EncodeRoles und DecodeRoles, um zwischen dem IEnumerable<string>-Format für Rollen und der base64-codierten Bitmaske basierend auf CustomRoles zu konvertieren, wie oben definiert:
    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. Verwenden Sie diese Methoden in einer benutzerdefinierten Implementierung von SecureDataFormat<AuthenticationTicket>. Zur Vereinfachung dieser Skizze delegiere ich den Großteil der Arbeit an OWIN-Standardkomponenten und implementiere einfach meine CustomTicketSerializer, die eine andere AuthenticationTicket erstellt und den Standard DataSerializers.Ticket verwendet. Dies ist offensichtlich nicht der effizienteste Weg, aber es zeigt, was Sie tun können:
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. Konfigurieren Sie in Ihrem Startup.cs OWIN, um Ihr benutzerdefiniertes Format zu verwenden, z.
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
  1. Fügen Sie in Ihrem OAuthAuthorizationServerProviderClaimTypes.Role dem ClaimsIdentity für jede dem Benutzer zugewiesene Rolle hinzu.

  2. Verwenden Sie in Ihrem Controller den Standard AuthorizeAttribute wie z

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

Der Einfachheit halber und zur Sicherheit können Sie die Klasse AuthorizeAttribute als Unterklasse angeben, um CustomRoles enum anstelle von string als Rollenkonfiguration zu akzeptieren.

9
SergGr

"Der Autorisierungsserver kann der gleiche Server wie der Ressourcenserver oder eine separate Entität sein." [RFC 6749, S. 6]

Wenn dies der Fall ist, können Sie den Cache-Speicher aktivieren. Das Token sollte jedoch für den Ressourcenserver verständlich sein und nicht zwischengespeichert werden müssen. Wenn dies ein Detail Ihrer Implementierung ist, ist ein Zwischenspeichern zwar möglich, aber nicht erforderlich.

0
Igor Kroeber

Der Hauptvorteil des Aktualisierungs-Token-Ansatzes besteht darin, die Anzahl der Datenbankabfragen zu reduzieren. Das Zugriffstoken hat die Ansprüche und ist signiert, sodass das Token ohne Abfrage der Datenbank als vertrauenswürdig eingestuft werden kann.

Das Zwischenspeichern des Zugriffstokens funktioniert, aber Sie müssen den Cache für jede Anforderung abfragen.

Es ist ein Kompromiss, zwischen dem Sie sich entscheiden müssen, n Minuten Verzögerung bei der Änderung der Zugriffsberechtigung vs. Anzahl der Abfragen, um die Gültigkeit des Zugriffstokens zu prüfen

Mit zusätzlicher Komplexität können Sie beides erreichen. In diesem Fall müssen Sie den Cache im Server-RAM und nur gesperrte Token speichern, um die Liste klein zu halten. Die Komplexität entsteht, wenn Sie mehrere Instanzen von Servern haben. Sie müssen diesen Cache der widerrufenen Token zwischen Ihren RS und AS synchron halten.

Wenn das Zugriffstoken gesperrt wird, muss der AS grundsätzlich alle RS informieren, um dieses Zugriffstoken zum Cache für zurückgenommenen Token hinzuzufügen.

Wenn eine Ressourcenanforderung vorliegt, prüft RS, ob das Token gesperrt ist oder nicht, und wenn die RS-Server die Server nicht widerrufen haben. Auf diese Weise ist der Overhead bei jeder Anforderung vorhanden, wird jedoch stark reduziert, da sich der Cache im Speicher befindet und die Anzahl der zurückgenommenen Token im Vergleich zur Anzahl der gültigen Token sehr geringer ist.

0
Bhoju

Ich hoffe, ich habe Ihre Fragen richtig beantwortet und kann ein paar Antworten geben:

1) Sie können es einlösen, wenn Sie Ihr AS so entwickelt haben, dass es bei jeder Benutzeranmeldung überprüft werden muss.

2) Ich denke, @ Hans Z. bedeutet, den Benutzer vom AS zu sperren. Wenn RS den Benutzer widerrufen, ändert dies nichts an der Tatsache, dass es sich immer noch um die von AS identifizierten handelt. Wenn AS den Benutzer jedoch widerrufen, wird die Verwendung seiner Identität verhindert.

3) In dem Artikel wird wahrscheinlich davon ausgegangen, dass die Autorisierung vom RS vorgenommen wird. AS ist nur dafür verantwortlich, Ihnen zu sagen, wer die Benutzer sind.

0
Majid ALSarra