paint-brush
Warum benötigen Sie JWT in Ihrem ASP.NET Core-Projekt?by@igorlopushko
7,145
7,145

Warum benötigen Sie JWT in Ihrem ASP.NET Core-Projekt?

Igor Lopushko16m2024/02/13
Read on Terminal Reader

In der Geschichte geht es darum, wie man eine Web-API erstellt, um JWT zu generieren und diese dann für die Autorisierung in der CRUD-Web-API zu verwenden.
featured image - Warum benötigen Sie JWT in Ihrem ASP.NET Core-Projekt?
Igor Lopushko HackerNoon profile picture
0-item

JWT steht für JSON Web Token und ist ein Autorisierungsmechanismus, keine Authentifizierung. Lassen Sie uns also herausfinden, was der Unterschied zwischen diesen beiden ist.


Bei der Authentifizierung handelt es sich um den Mechanismus, mit dem überprüft werden kann, ob der Benutzer genau derjenige ist, für den er sich ausgibt. Es handelt sich um einen Anmeldevorgang, bei dem ein Benutzer einen Benutzernamen und ein Passwort angibt und das System diese überprüft. Die Authentifizierung beantwortet also die Frage: Wer ist der Benutzer?


Autorisierung ist der Mechanismus, der die Überprüfung ermöglicht, welche Zugriffsrechte der Benutzer auf eine bestimmte Ressource hat. Dabei handelt es sich um einen Prozess, bei dem Benutzern einige Rollen und eine Reihe von Berechtigungen gewährt werden, über die eine bestimmte Rolle verfügt. Die Autorisierung beantwortet also die Frage: Welche Rechte hat der Benutzer im System?

Authentifizierung vs. Autorisierung


Es ist wichtig zu verstehen, dass die Authentifizierung immer an erster Stelle und die Autorisierung an zweiter Stelle steht. Mit anderen Worten: Sie können keine Erlaubnis erhalten, bevor Sie Ihre Identität überprüft haben. Doch welche sind die beliebtesten Autorisierungsmethoden? Es gibt zwei Hauptansätze zur Handhabung der Autorisierung für die Webanwendung.

Sitzungen

Ein traditioneller Ansatz im Web für Autorisierungsbenutzer ist eine Cookie-basierte serverseitige Sitzung. Der Prozess beginnt, wenn sich ein Benutzer anmeldet und ein Server ihn authentifiziert. Anschließend erstellt der Server eine Sitzung mit einer Sitzungs-ID und speichert diese irgendwo im Speicher des Servers. Der Server sendet die Sitzungs-ID an den Client zurück und der Client speichert die Sitzungs-ID in Cookies. Bei jeder Anfrage sendet der Client eine Sitzungs-ID als Teil der Anfrage, und der Server überprüft die Sitzungs-ID in seinem Speicher und die Berechtigungen des Benutzers für diese Sitzung.

Sitzungsbasierte Autorisierung

Token

Ein weiterer beliebter Ansatz ist die Verwendung von Token zur Autorisierung. Der Prozess beginnt auf ähnliche Weise, wenn ein Benutzer Login und Passwörter eingibt und ein Client eine Login-Anfrage an einen Server sendet. Anstatt eine Sitzung zu erstellen, generiert der Server ein mit dem geheimen Token signiertes Token. Anschließend sendet der Server das Token an den Client zurück und der Client muss es in einem lokalen Speicher speichern. Ähnlich wie beim sitzungsbasierten Ansatz muss der Client für jede Anfrage ein Token an den Server senden. Der Server speichert jedoch keine zusätzlichen Informationen über die Benutzersitzung. Der Server muss bestätigen, dass sich das Token seit seiner Erstellung und Signatur mit dem geheimen Schlüssel nicht geändert hat.

Tokenbasierte Autorisierung

Sitzung vs. Token

Der sitzungsbasierte Autorisierungsansatz kann anfällig für einen Angriff sein, der als Cross-Site Request Forgery (CSRF) bezeichnet wird. Dabei handelt es sich um eine Art Angriff, bei dem der Angreifer auf eine Website verweist, auf der er angemeldet ist, um Aktionen auszuführen, die er nicht beabsichtigt hatte, etwa eine Zahlung zu tätigen oder ein Passwort zu ändern.


Eine weitere Sache ist, dass bei Verwendung eines sitzungsbasierten Autorisierungsansatzes eine zustandsbehaftete Sitzung zwischen einem Client und einem Server erstellt wird. Das Problem besteht darin, dass, wenn ein Client auf verschiedene Server im Rahmen derselben Anwendung zugreifen möchte, diese Server einen gemeinsamen Sitzungsstatus haben müssen. In einem anderen Fall muss der Client auf jedem Server autorisiert werden, da die Sitzung unterschiedlich sein wird.

Sitzungsbasierte Freigabe des Autorisierungsstatus


Andererseits erfordert der tokenbasierte Autorisierungsansatz keine Speicherung von Sitzungsdaten auf der Serverseite und kann die Autorisierung zwischen mehreren Servern vereinfachen.


Allerdings können Token immer noch von einem Angreifer gestohlen werden und es kann auch schwierig sein, Token ungültig zu machen. In diesem Artikel erfahren Sie mehr über die Details und den Umgang mit der Ungültigmachung.

JWT

JSON Web Token (JWT) ist ein offener Standard, der eine kompakte und eigenständige Möglichkeit zur sicheren Übertragung von Informationen zwischen Parteien als JSON-Objekt definiert. Diese Informationen können überprüft und vertrauenswürdig sein, da sie digital signiert sind. JWTs können mit einem Geheimnis (mit dem HMAC- Algorithmus) oder einem öffentlichen/privaten Schlüsselpaar mit RSA oder ECDSA signiert werden.

JWT-Struktur

JSON-Web-Tokens bestehen aus drei durch Punkte getrennten Teilen .


  • Header
 { "alg": "HS256", "typ": "JWT" }

Der Header besteht normalerweise aus zwei Teilen: der Art des Tokens und dem verwendeten Signaturalgorithmus.


  • Nutzlast
 { "sub": "1234567890", "name": "John Doe", "admin": true }

Die Nutzlast enthält die Ansprüche, bei denen es sich um Aussagen über den Benutzer handelt. Die Nutzlast wird dann Base64Url- codiert, um den zweiten Teil des JSON-Web-Tokens zu bilden. Eine Beschreibung der Standardfelder, die als Ansprüche verwendet werden, finden Sie hier .


  • Unterschrift
 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Um den Signaturteil zu erstellen, müssen Sie den codierten Header, die codierte Nutzlast, ein Geheimnis und den im Header angegebenen Algorithmus nehmen und diesen signieren.


Das Token sieht normalerweise wie folgt aus:

xxxxx.yyyyy.zzzzz


Sie können zu jwt.io navigieren und ein Beispiel-Token oder Ihr eigenes debuggen. Fügen Sie einfach Ihren Token in das Feld „Verschlüsselt“ ein und wählen Sie den Algorithmus der Token-Signatur aus.

jwt.io-Debugger

.NET-Projekt

Da wir nun theoretische Kenntnisse über die Funktionsweise von JWT haben, können wir diese auf das reale Projekt anwenden. Nehmen wir an, wir haben eine einfache API, die CRUD-Operationen für die Kaffee-Entität darstellt. Wir werden ein ASP.NET Core API-Projekt erstellen, das die Coffee API darstellt. Danach erstellen wir ein weiteres ASP.NET Core-API-Projekt, das eine Identitäts-API darstellt, die JWT generieren könnte. Im wirklichen Leben würden Sie wahrscheinlich Identity Server oder Okta oder Auth0 für Authentifizierungs-/Autorisierungszwecke verwenden. Wir würden jedoch unsere eigene Identitäts-API erstellen, um zu demonstrieren, wie JWT generiert wird. Wenn die Identity API fertig ist, können wir ihren Controller aufrufen und JWT basierend auf den Benutzerdaten generieren. Außerdem können wir die Coffee-API mit einer Autorisierungskonfiguration schützen, die die Übergabe von JWT bei jeder Anfrage erfordert.

.NET-Projektlandschaft

Kaffee-API

Zuerst erstellen wir ein einfaches ASP.NET Core API-Projekt, das die Coffee API darstellt. Hier ist die Struktur dieses Projekts:

Kaffee-API – Projektstruktur


Beginnen wir mit Coffee.cs im Model Ordner. Es handelt sich um eine einfache Entität mit den Eigenschaften Id und Name .

 namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } }


Wir müssen unsere Entitäten speichern, während wir mit der API arbeiten. Lassen Sie uns also einen einfachen In-Memory-Speicher einführen. Es befindet sich in der Datei Storage.cs im Ordner Data .

 namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } }


Wir benötigen eine Klasse, die Anfragen an die Coffee-API darstellt. Erstellen wir also CoffeeRequest.cs im Ordner Contracts .

 namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } }


Wenn es fertig ist, können wir CoffeeController.cs im Controller Ordner implementieren, der CRUD-Operationen für die Kaffee-Entität darstellt.

 using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } }


Die Coffee-API ist fertig und wir können das Projekt ausführen und die Swagger-Benutzeroberfläche wie folgt sehen:

Kaffee-API – Swagger-Benutzeroberfläche

Identitäts-API

Erstellen wir ein weiteres ASP.NET Core-API-Projekt, das die Identitäts-API darstellt. Hier ist die Struktur dieses Projekts:

Identitäts-API – Projektstruktur

Beginnen wir mit der Datei „ TokenGenerationRequest.cs im Ordner Contracts “, die die Anforderung für die Generierung eines neuen JWT mit den Eigenschaften Email “ und Password darstellt.

 namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } }


Wir müssen nur TokenController.cs implementieren, das die Logik der JWT-Generierung darstellt. Aber bevor wir das tun, muss das NuGet-Paket Microsoft.AspNetCore.Authentication.JwtBearer installiert werden.

 using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } }


Beachten Sie, dass sensible Konstanten wie SecretKey , Issuer und Audience irgendwo in der Konfiguration platziert werden müssen. Sie sind nur zur Vereinfachung dieses Testprojekts fest codiert. Das Feld Lifetime ist auf 20 Minuten eingestellt, was bedeutet, dass der Token für diesen Zeitraum gültig ist. Sie können diesen Parameter auch konfigurieren.


Jetzt können wir das Projekt ausführen und die Swagger-Benutzeroberfläche wie folgt sehen:

Identitäts-API – Swagger-Benutzeroberfläche


Rufen wir den Endpunkt /token auf und generieren ein neues JWT. Probieren Sie die folgende Nutzlast aus:

 { "email": "[email protected]", "password": "password" }


Die Identity API generiert das entsprechende JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM

Aktivieren der Autorisierung in der Coffee API

Wenn die Identity API nun bereit ist und uns Token zur Verfügung stellt, können wir die Coffee API mit Autorisierung schützen. Auch hier muss das Microsoft.AspNetCore.Authentication.JwtBearer NuGet-Paket installiert werden.


Wir müssen die erforderlichen Dienste durch Authentifizierungsdienste registrieren. Fügen Sie den folgenden Code direkt nach dem Erstellen eines Builders zur Datei Program.cs hinzu.

 var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization();


Es ist wichtig, sich daran zu erinnern, dass die Reihenfolge in der Middleware wichtig ist. Wir aktivieren die Authentifizierung, indem wir die Methode AddAuthentication() aufrufen und JwtBearerDefaults.AuthenticationScheme als Authentifizierungsschema angeben. Es handelt sich um eine Konstante, die einen Bearer Wert enthält.

 namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } }


Wir müssen TokenValidationParameters angeben, das beschreibt, welche Parameter von JWT während der Autorisierung validiert werden. Wir geben auch IssuerSigningKey ähnlich wie signingCredentials in der Identity API an, um die JWT-Signatur zu überprüfen. Weitere Details zu TokenValidationParameters finden Sie hier .


Der nächste Codeteil fügt dem Builder Middleware hinzu, die Authentifizierungs- und Autorisierungsfunktionen ermöglicht. Es sollte zwischen den Methoden UseHttpsRedirection() und MapControllers() hinzugefügt werden.

 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();


Jetzt können wir das Authorize Attribut für den Controller oder seine Aktionen verwenden. Durch die Anwendung dieses Codes werden nun alle Aktionen in CoffeeController durch einen Autorisierungsmechanismus geschützt und JWT muss als Teil der Anfrage gesendet werden.

 [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { ..


Wenn wir einen beliebigen Endpunkt der Coffee-API aufrufen, können wir HttpContext.User debuggen und sehen, dass es gefüllt ist und eine Identity mit Ansprüchen hat, die wir in JWT angegeben haben. Dies ist wichtig, um zu verstehen, wie ASP.NET Core die Autorisierung unter der Haube handhabt.

Coffee API – Ansprüche werden aus dem JWT ausgefüllt

Autorisierung zur Swagger-Benutzeroberfläche hinzufügen

Wir haben großartige Arbeit geleistet, um die Coffee API mit der Autorisierung zu schützen. Wenn Sie jedoch das Coffee-API-Projekt ausführen und die Swagger-Benutzeroberfläche öffnen, können Sie JWT nicht als Teil der Anfrage senden. Um das zu beheben, müssen wir die Datei Program.cs mit dem folgenden Code aktualisieren:

 builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); });


Danach können wir die Schaltfläche „Autorisieren“ in der rechten oberen Ecke sehen:

Kaffee-API – Die Schaltfläche „Autorisieren“ wurde angezeigt


Wenn Sie auf die Schaltfläche „Autorisieren“ klicken, können Sie JWT wie folgt eingeben:

Kaffee-API – Geben Sie den JWT-Wert ein

Verwenden Sie Postman zum Testen

Sie können sich nicht auf die Verwendung der Swagger-Benutzeroberfläche beschränken und können die API über das Postman-Tool testen. Rufen wir zunächst den /token Endpunkt der Identitäts-API auf. Wir müssen Content-Type -Header mit dem Wert application/json im Abschnitt Headers angeben, da wir JSON als Nutzlast verwenden werden.

Identitäts-API – Header angeben


Danach können wir den Endpunkt /token aufrufen und ein neues JWT erhalten.

Identitäts-API – JWT generieren


Jetzt können wir JWT kopieren und damit die Coffee API aufrufen. Wir müssen einen Content-Type Header ähnlich der Identity API angeben, wenn wir Endpunkte testen, erstellen und aktualisieren möchten. Der Authorization muss außerdem mit dem Wert Bearer [your JWT value] festgelegt werden. Klicken Sie anschließend einfach auf die Schaltfläche „Senden“ und sehen Sie sich das Ergebnis an.

Coffee API – Alle Entitäten abrufen

Rollenbasierte Autorisierung

Wie Sie sich erinnern, besteht der Nutzlastteil von JWT aus einer Reihe von Ansprüchen mit Werten, die genau Schlüssel-Wert-Paare sind. Mit der rollenbasierten Autorisierung können Sie den Zugriff auf Anwendungsressourcen abhängig von der Rolle, der der Benutzer angehört, differenzieren.


Wenn wir die Create() Methode in der Datei TokenController.cs in der Identity API mit dem Code aktualisieren, der einen neuen Anspruch für die Rolle hinzufügt; Wir können die rollenbasierte Authentifizierung in der Coffee API durchführen. ClaimTypes.Role ist ein vordefinierter Name des Rollenanspruchs.

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") };


Aktualisieren Sie das Authorize Attribut in der Datei CoffeeController.cs und geben Sie den Rollennamen an:

 [Authorize(Roles = "Barista")]


Jetzt müssen alle Benutzer, die die Coffee API aufrufen, über den Rollenanspruch mit dem Barista Wert verfügen. Andernfalls erhalten sie den Statuscode 403 Forbidden .

Anspruchsbasierte Autorisierung

Ein Authorize Attribut kann die rollenbasierte Authentifizierung problemlos handhaben. Was aber, wenn dies nicht ausreicht und wir den Zugriff anhand einiger Benutzereigenschaften wie Alter oder anderer differenzieren möchten? Sie haben wahrscheinlich bereits erraten, dass Sie Ihre Ansprüche zu JWT hinzufügen und zum Aufbau einer Autorisierungslogik verwenden können. Die rollenbasierte Autorisierung selbst ist ein Sonderfall der anspruchsbasierten Autorisierung, ebenso wie eine Rolle dasselbe Anspruchsobjekt eines vordefinierten Typs ist.


Aktualisieren wir die Methode Create() in der Datei TokenController.cs in der Identity API mit dem Code, der einen neuen Anspruch IsGourmet hinzufügt.

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") };


In der Datei Program.cs in der Coffee API müssen wir eine Richtlinie erstellen, die einen Anspruch überprüft und im Authorize Attribut verwendet werden kann. Der folgende Code muss direkt nach dem Aufruf der AddAuthentication() -Methode hinzugefügt werden.

 builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); });


Aktualisieren Sie das Authorize Attribut in der Datei CoffeeController.cs und geben Sie den Richtliniennamen an:

 [Authorize(Policy = "OnlyForGourmet")]

Zusammenfassung

Glückwunsch! Sie haben große Anstrengungen unternommen, um JWT in .NET zu erlernen. Jetzt müssen Sie ein solides Verständnis der JWT-Prinzipien haben und wissen, warum es wichtig ist, sie für die Autorisierung in .NET-Anwendungen zu verwenden. Im Bereich der Authentifizierung und Autorisierung in ASP.NET Core-Anwendungen haben wir jedoch nur an der Oberfläche gekratzt.


Ich schlage vor, einen Blick in die Microsoft-Dokumentation zu den Themen zu werfen, die wir in diesem Artikel besprochen haben. Darüber hinaus verfügt die .NET-Plattform über zahlreiche integrierte Funktionen zur Autorisierung und Rollenverwaltung. Eine gute Ergänzung zu diesem Artikel könnte die Microsoft- Dokumentation zur Autorisierung sein.