Token-basierte Authentifizierung in ASP.NET Core

161

Ich arbeite mit der ASP.NET Core-Anwendung. Ich versuche, die Token-basierte Authentifizierung zu implementieren, kann jedoch nicht herausfinden, wie das neue Sicherheitssystem für meinen Fall verwendet wird. Ich habe Beispiele durchgesehen, aber sie haben mir nicht viel geholfen. Sie verwenden entweder die Cookie-Authentifizierung oder die externe Authentifizierung (GitHub, Microsoft, Twitter).

Was mein Szenario ist: angularjs Anwendung sollte eine /tokenURL anfordern, die Benutzername und Passwort übergibt. WebApi sollte den Benutzer autorisieren und zurückgeben, access_tokenwas von der anglejs-App bei folgenden Anforderungen verwendet wird.

Ich habe einen großartigen Artikel über die Implementierung genau dessen gefunden, was ich in der aktuellen Version von ASP.NET - Token Based Authentication mit ASP.NET Web API 2, Owin und Identity benötige . Für mich ist jedoch nicht klar, wie ich dasselbe in ASP.NET Core tun soll.

Meine Frage lautet: Wie konfiguriere ich die ASP.NET Core WebApi-Anwendung für die tokenbasierte Authentifizierung?

Gewähren
quelle
Ich hatte das gleiche Problem und hatte vor, das alles selbst zu tun. Zu Ihrer Information, es gibt noch eine andere Frage stackoverflow.com/questions/29055477/… aber noch keine Antwort, mal sehen, was passiert
Son_of_Sam
Ich habe auch das gleiche Problem, konnte aber noch keine Lösung finden ... Ich muss eine benutzerdefinierte Authentifizierung mit einem anderen Dienst schreiben, der mein Token authentifiziert.
Mayank Gupta

Antworten:

137

Update für .Net Core 3.1:

David Fowler (Architekt des ASP .NET Core-Teams) hat eine unglaublich einfache Reihe von Aufgabenanwendungen zusammengestellt, einschließlich einer einfachen Anwendung, die JWT demonstriert . Ich werde bald seine Updates und seinen simplen Stil in diesen Beitrag aufnehmen.

Aktualisiert für .Net Core 2:

In früheren Versionen dieser Antwort wurde RSA verwendet. Es ist wirklich nicht notwendig, wenn derselbe Code, der die Token generiert, auch die Token überprüft. Wenn Sie jedoch die Verantwortung verteilen, möchten Sie dies wahrscheinlich immer noch mit einer Instanz von tun Microsoft.IdentityModel.Tokens.RsaSecurityKey.

  1. Erstellen Sie einige Konstanten, die wir später verwenden werden. Folgendes habe ich getan:

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
  2. Fügen Sie dies zu Ihren Startup.cs hinzu ConfigureServices. Wir werden die Abhängigkeitsinjektion später verwenden, um auf diese Einstellungen zuzugreifen. Ich gehe davon aus, dass Sie authenticationConfigurationein ConfigurationSectionoder ein ConfigurationObjekt sind, sodass Sie eine andere Konfiguration für Debug und Produktion haben können. Stellen Sie sicher, dass Sie Ihren Schlüssel sicher aufbewahren! Es kann eine beliebige Zeichenfolge sein.

    var keySecret = authenticationConfiguration["JwtSigningKey"];
    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret));
    
    services.AddTransient(_ => new JwtSignInHandler(symmetricKey));
    
    services.AddAuthentication(options =>
    {
        // This causes the default authentication scheme to be JWT.
        // Without this, the Authorization header is not checked and
        // you'll get no results. However, this also means that if
        // you're already using cookies in your app, they won't be 
        // checked by default.
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters.ValidateIssuerSigningKey = true;
            options.TokenValidationParameters.IssuerSigningKey = symmetricKey;
            options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
            options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
        });

    Ich habe gesehen, wie andere Antworten andere Einstellungen geändert haben, wie z ClockSkew. Die Standardeinstellungen sind so festgelegt, dass sie für verteilte Umgebungen funktionieren, deren Uhren nicht genau synchron sind. Dies sind die einzigen Einstellungen, die Sie ändern müssen.

  3. Authentifizierung einrichten. Sie sollten diese Zeile vor jeder Middleware haben, die Ihre UserInformationen benötigt , wie z app.UseMvc().

    app.UseAuthentication();

    Beachten Sie, dass dies nicht dazu führt, dass Ihr Token mit dem SignInManageroder etwas anderem ausgegeben wird. Sie müssen Ihren eigenen Mechanismus für die Ausgabe Ihres JWT bereitstellen - siehe unten.

  4. Möglicherweise möchten Sie eine angeben AuthorizationPolicy. Auf diese Weise können Sie Controller und Aktionen angeben, die nur Bearer-Token als Authentifizierung verwenden [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
  5. Hier kommt der schwierige Teil: das Erstellen des Tokens.

    class JwtSignInHandler
    {
        public const string TokenAudience = "Myself";
        public const string TokenIssuer = "MyProject";
        private readonly SymmetricSecurityKey key;
    
        public JwtSignInHandler(SymmetricSecurityKey symmetricKey)
        {
            this.key = symmetricKey;
        }
    
        public string BuildJwt(ClaimsPrincipal principal)
        {
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(
                issuer: TokenIssuer,
                audience: TokenAudience,
                claims: principal.Claims,
                expires: DateTime.Now.AddMinutes(20),
                signingCredentials: creds
            );
    
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }

    Dann in Ihrem Controller, wo Sie Ihr Token wollen, so etwas wie das Folgende:

    [HttpPost]
    public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory)
    {
        var principal = new System.Security.Claims.ClaimsPrincipal(new[]
        {
            new System.Security.Claims.ClaimsIdentity(new[]
            {
                new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User")
            })
        });
        return tokenFactory.BuildJwt(principal);
    }

    Hier gehe ich davon aus, dass Sie bereits einen Schulleiter haben. Wenn Sie Identität verwenden, können Sie IUserClaimsPrincipalFactory<>Ihre Userin eine verwandeln ClaimsPrincipal.

  6. So testen Sie es : Holen Sie sich ein Token und fügen Sie es in das Formular unter jwt.io ein . Mit den oben angegebenen Anweisungen können Sie auch das Geheimnis Ihrer Konfiguration verwenden, um die Signatur zu validieren!

  7. Wenn Sie dies in einer Teilansicht auf Ihrer HTML-Seite in Kombination mit der Nur-Träger-Authentifizierung in .Net 4.5 gerendert haben, können Sie jetzt a verwenden ViewComponent, um dasselbe zu tun. Es ist größtenteils das gleiche wie der obige Controller-Aktionscode.

Matt DeKrey
quelle
1
Sie müssen tatsächlich injizieren IOptions<OAuthBearerAuthenticationOptions>, um die Optionen zu verwenden. Die direkte Verwendung eines Optionsobjekts wird aufgrund der benannten Konfiguration, die vom Optionsmodell-Framework unterstützt wird, nicht unterstützt.
Matt DeKrey
2
Aktualisiert auf das, was ich verwende, obwohl die Antwort jetzt neu geschrieben werden sollte. Danke, dass du mich gestoßen hast!
Matt DeKrey
5
# 5 wurde seitdem in Microsoft.AspNet.Authentication.OAuthBearer - Beta 5 - 6 und möglicherweise früheren Betas wie folgt geändert, diese jedoch nicht bestätigt. auth.AddPolicy ("Bearer", neuer AuthorizationPolicyBuilder () .AddAuthenticationSchemes (OAuthBearerAuthenticationDefaults.AuthenticationScheme) .RequireAuthenticatedUser (). Build ());
Dynamiclynk
5
@MattDeKrey Ich habe diese Antwort als Ausgangspunkt für ein Beispiel für eine einfache tokenbasierte Authentifizierung verwendet und sie aktualisiert, um gegen Beta 7 zu arbeiten - siehe github.com/mrsheepuk/ASPNETSelfCreatedTokenAuthExample - enthält auch einige der Hinweise aus diesen Kommentaren.
Mark Hughes
2
Erneut aktualisiert für RC1 - alte Versionen für Beta7 und Beta8 in Filialen auf GitHub verfügbar.
Mark Hughes
83

Ausgehend von Matt Dekreys fabelhafter Antwort habe ich ein voll funktionsfähiges Beispiel für die tokenbasierte Authentifizierung erstellt, das gegen ASP.NET Core (1.0.1) arbeitet. Den vollständigen Code finden Sie in diesem Repository auf GitHub (alternative Zweige für 1.0.0-rc1 , Beta8 , Beta7) ). , die wichtigsten Schritte sind:

Generieren Sie einen Schlüssel für Ihre Anwendung

In meinem Beispiel generiere ich bei jedem Start der App einen zufälligen Schlüssel. Sie müssen einen generieren, irgendwo speichern und Ihrer Anwendung zur Verfügung stellen. In dieser Datei erfahren Sie, wie ich einen zufälligen Schlüssel generiere und wie Sie ihn aus einer JSON-Datei importieren können . Wie in den Kommentaren von @kspearrin vorgeschlagen, scheint die Datenschutz-API ein idealer Kandidat für die "korrekte" Verwaltung der Schlüssel zu sein, aber ich habe noch nicht herausgefunden, ob dies möglich ist. Bitte senden Sie eine Pull-Anfrage, wenn Sie es ausarbeiten!

Startup.cs - ConfigureServices

Hier müssen wir einen privaten Schlüssel laden, mit dem unsere Token signiert werden sollen, mit dem wir auch die Token überprüfen, wenn sie präsentiert werden. Wir speichern den Schlüssel in einer Variablen auf Klassenebene, keydie wir in der folgenden Configure-Methode wiederverwenden. TokenAuthOptions ist eine einfache Klasse, die die Signaturidentität, die Zielgruppe und den Aussteller enthält, die wir im TokenController zum Erstellen unserer Schlüssel benötigen.

// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();

// Create the key, and a set of token options to record signing credentials 
// using that key, along with the other parameters we will need in the 
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
    Audience = TokenAudience,
    Issuer = TokenIssuer,
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};

// Save the token options into an instance so they're accessible to the 
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);

// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
        .RequireAuthenticatedUser().Build());
});

Wir haben auch eine Autorisierungsrichtlinie eingerichtet, die es uns ermöglicht, sie [Authorize("Bearer")]auf den Endpunkten und Klassen zu verwenden, die wir schützen möchten.

Startup.cs - Konfigurieren

Hier müssen wir die JwtBearerAuthentication konfigurieren:

app.UseJwtBearerAuthentication(new JwtBearerOptions {
    TokenValidationParameters = new TokenValidationParameters {
        IssuerSigningKey = key,
        ValidAudience = tokenOptions.Audience,
        ValidIssuer = tokenOptions.Issuer,

        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,

        // This defines the maximum allowable clock skew - i.e.
        // provides a tolerance on the token expiry time 
        // when validating the lifetime. As we're creating the tokens 
        // locally and validating them on the same machines which 
        // should have synchronised time, this can be set to zero. 
        // Where external tokens are used, some leeway here could be 
        // useful.
        ClockSkew = TimeSpan.FromMinutes(0)
    }
});

TokenController

Im Token-Controller benötigen Sie eine Methode zum Generieren signierter Schlüssel mit dem in Startup.cs geladenen Schlüssel. Wir haben eine TokenAuthOptions-Instanz in Startup registriert, daher müssen wir diese in den Konstruktor für TokenController einfügen:

[Route("api/[controller]")]
public class TokenController : Controller
{
    private readonly TokenAuthOptions tokenOptions;

    public TokenController(TokenAuthOptions tokenOptions)
    {
        this.tokenOptions = tokenOptions;
    }
...

Dann müssen Sie das Token in Ihrem Handler für den Anmeldeendpunkt generieren. In meinem Beispiel nehme ich einen Benutzernamen und ein Kennwort und überprüfe diese mithilfe einer if-Anweisung. Der Schlüssel, den Sie tun müssen, ist jedoch, Ansprüche zu erstellen oder zu laden -basierte Identität und generieren Sie das Token dafür:

public class AuthRequest
{
    public string username { get; set; }
    public string password { get; set; }
}

/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
    // Obviously, at this point you need to validate the username and password against whatever system you wish.
    if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
    {
        DateTime? expires = DateTime.UtcNow.AddMinutes(2);
        var token = GetToken(req.username, expires);
        return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
    }
    return new { authenticated = false };
}

private string GetToken(string user, DateTime? expires)
{
    var handler = new JwtSecurityTokenHandler();

    // Here, you should create or look up an identity for the user which is being authenticated.
    // For now, just creating a simple generic identity.
    ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });

    var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
        Issuer = tokenOptions.Issuer,
        Audience = tokenOptions.Audience,
        SigningCredentials = tokenOptions.SigningCredentials,
        Subject = identity,
        Expires = expires
    });
    return handler.WriteToken(securityToken);
}

Und das sollte es sein. Fügen [Authorize("Bearer")]Sie einfach eine Methode oder Klasse hinzu, die Sie schützen möchten, und Sie sollten eine Fehlermeldung erhalten, wenn Sie versuchen, ohne vorhandenes Token darauf zuzugreifen. Wenn Sie einen 401-Fehler anstelle eines 500-Fehlers zurückgeben möchten, müssen Sie einen benutzerdefinierten Ausnahmebehandler registrieren, wie ich ihn in meinem Beispiel hier habe .

Mark Hughes
quelle
1
Dies ist ein wirklich hervorragendes Beispiel und enthält alle fehlenden Teile, die ich brauchte, um das Beispiel von @ MattDeKrey zum Laufen zu bringen. Vielen Dank! Beachten Sie, dass jeder, der noch Beta7 anstelle von Beta8 zielt, dieses Beispiel in der Github-Geschichte finden kann
Nickspoon
1
Ich bin froh, dass es @nickspoon geholfen hat - wenn Sie überhaupt Probleme damit haben, lassen Sie es mich wissen oder senden Sie eine Pull-Anfrage auf github und ich werde aktualisieren!
Mark Hughes
2
Vielen Dank dafür, aber ich verstehe nicht ganz, warum etwas, das in der ASP.Net 4-Web-API sofort funktioniert hat, in ASP.Net 5 einiges an Konfiguration erfordert. Scheint ein Rückschritt zu sein.
JMK
1
Ich denke, sie drängen wirklich auf "Social Auth" für ASP.NET 5, was vermutlich Sinn macht, aber es gibt Anwendungen, die nicht geeignet sind, daher bin ich mir nicht sicher, ob ich ihrer Richtung @JMK zustimme
Mark Hughes
1
@YuriyP Ich muss diese Antwort für RC2 aktualisieren - Ich habe unsere interne App, die dies verwendet, noch nicht für RC2 aktualisiert, daher bin ich mir nicht sicher, worum es geht. Ich werde aktualisieren, sobald ich den Unterschied herausgefunden habe ...
Mark Hughes
3

Sie können sich die OpenId Connect-Beispiele ansehen, die veranschaulichen, wie mit verschiedenen Authentifizierungsmechanismen umgegangen wird, einschließlich JWT-Token:

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples

Wenn Sie sich das Cordova Backend-Projekt ansehen, sieht die Konfiguration für die API folgendermaßen aus:

           // Create a new branch where the registered middleware will be executed only for non API calls.
        app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch => {
            // Insert a new cookies middleware in the pipeline to store
            // the user identity returned by the external identity provider.
            branch.UseCookieAuthentication(new CookieAuthenticationOptions {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                AuthenticationScheme = "ServerCookie",
                CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie",
                ExpireTimeSpan = TimeSpan.FromMinutes(5),
                LoginPath = new PathString("/signin"),
                LogoutPath = new PathString("/signout")
            });

            branch.UseGoogleAuthentication(new GoogleOptions {
                ClientId = "560027070069-37ldt4kfuohhu3m495hk2j4pjp92d382.apps.googleusercontent.com",
                ClientSecret = "n2Q-GEw9RQjzcRbU3qhfTj8f"
            });

            branch.UseTwitterAuthentication(new TwitterOptions {
                ConsumerKey = "6XaCTaLbMqfj6ww3zvZ5g",
                ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI"
            });
        });

Die Logik in /Providers/AuthorizationProvider.cs und der RessourceController dieses Projekts sind ebenfalls einen Blick wert;).

Alternativ können Sie auch den folgenden Code verwenden, um Token zu validieren (es gibt auch ein Snippet, damit es mit signalR funktioniert):

        // Add a new middleware validating access tokens.
        app.UseOAuthValidation(options =>
        {
            // Automatic authentication must be enabled
            // for SignalR to receive the access token.
            options.AutomaticAuthenticate = true;

            options.Events = new OAuthValidationEvents
            {
                // Note: for SignalR connections, the default Authorization header does not work,
                // because the WebSockets JS API doesn't allow setting custom parameters.
                // To work around this limitation, the access token is retrieved from the query string.
                OnRetrieveToken = context =>
                {
                    // Note: when the token is missing from the query string,
                    // context.Token is null and the JWT bearer middleware will
                    // automatically try to retrieve it from the Authorization header.
                    context.Token = context.Request.Query["access_token"];

                    return Task.FromResult(0);
                }
            };
        });

Für die Ausgabe von Token können Sie die openId Connect-Serverpakete wie folgt verwenden:

        // Add a new middleware issuing access tokens.
        app.UseOpenIdConnectServer(options =>
        {
            options.Provider = new AuthenticationProvider();
            // Enable the authorization, logout, token and userinfo endpoints.
            //options.AuthorizationEndpointPath = "/connect/authorize";
            //options.LogoutEndpointPath = "/connect/logout";
            options.TokenEndpointPath = "/connect/token";
            //options.UserinfoEndpointPath = "/connect/userinfo";

            // Note: if you don't explicitly register a signing key, one is automatically generated and
            // persisted on the disk. If the key cannot be persisted, an exception is thrown.
            // 
            // On production, using a X.509 certificate stored in the machine store is recommended.
            // You can generate a self-signed certificate using Pluralsight's self-cert utility:
            // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip
            // 
            // options.SigningCredentials.AddCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75");
            // 
            // Alternatively, you can also store the certificate as an embedded .pfx resource
            // directly in this assembly or in a file published alongside this project:
            // 
            // options.SigningCredentials.AddCertificate(
            //     assembly: typeof(Startup).GetTypeInfo().Assembly,
            //     resource: "Nancy.Server.Certificate.pfx",
            //     password: "Owin.Security.OpenIdConnect.Server");

            // Note: see AuthorizationController.cs for more
            // information concerning ApplicationCanDisplayErrors.
            options.ApplicationCanDisplayErrors = true // in dev only ...;
            options.AllowInsecureHttp = true // in dev only...;
        });

BEARBEITEN: Ich habe eine Einzelseitenanwendung mit tokenbasierter Authentifizierungsimplementierung unter Verwendung des Aurelia-Front-End-Frameworks und des ASP.NET-Kerns implementiert. Es besteht auch eine dauerhafte Verbindung mit Signal R. Ich habe jedoch keine DB-Implementierung durchgeführt. Code kann hier eingesehen werden: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

Hoffe das hilft,

Beste,

Alex

Darxtar
quelle
1

Schauen Sie sich OpenIddict an - es ist ein neues Projekt (zum Zeitpunkt des Schreibens), mit dem die Erstellung von JWT-Token und Aktualisierungstoken in ASP.NET 5 einfach konfiguriert werden kann. Die Validierung der Token wird von anderer Software durchgeführt.

Angenommen, Sie verwenden Identitymit Entity Framework, ist die letzte Zeile das, was Sie Ihrer ConfigureServicesMethode hinzufügen würden :

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddOpenIddictCore<Application>(config => config.UseEntityFramework());

In Configurehaben Sie OpenIddict so eingerichtet, dass JWT-Token bereitgestellt werden:

app.UseOpenIddictCore(builder =>
{
    // tell openiddict you're wanting to use jwt tokens
    builder.Options.UseJwtTokens();
    // NOTE: for dev consumption only! for live, this is not encouraged!
    builder.Options.AllowInsecureHttp = true;
    builder.Options.ApplicationCanDisplayErrors = true;
});

Sie konfigurieren die Validierung von Token auch in Configure:

// use jwt bearer authentication
app.UseJwtBearerAuthentication(options =>
{
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.RequireHttpsMetadata = false;
    options.Audience = "http://localhost:58292/";
    options.Authority = "http://localhost:58292/";
});

Es gibt ein oder zwei weitere Kleinigkeiten, z. B. dass Ihr DbContext von OpenIddictContext abgeleitet werden muss.

In diesem Blogbeitrag finden Sie eine ausführliche Erklärung: http://capesean.co.za/blog/asp-net-5-jwt-tokens/

Eine funktionierende Demo finden Sie unter: https://github.com/capesean/openiddict-test

Sean
quelle