it-swarm.com.de

JWT-Authentifizierung für die ASP.NET-Web-API

Ich versuche, das JWT-Inhabertoken (JSON-Web-Token) in meiner Web-API-Anwendung zu unterstützen, und verliere mich.

Ich sehe Unterstützung für .NET Core und für OWIN-Anwendungen.
Ich hoste derzeit meine Anwendung in IIS.

Wie kann ich dieses Authentifizierungsmodul in meiner Anwendung erreichen? Kann ich die <authentication> -Konfiguration auf ähnliche Weise wie die Formular-/Windows-Authentifizierung verwenden?

214
Amir Popovich

Ich beantwortete diese Frage: So sichern Sie eine ASP.NET-Web-API vor 4 Jahren mit HMAC.

Inzwischen hat sich eine Menge an Sicherheitsaspekten geändert, insbesondere JWT wird immer beliebter. Hier werde ich versuchen zu erklären, wie JWT auf einfachste und einfachste Weise verwendet wird, damit wir uns nicht aus dem Dschungel von OWIN, Oauth2, ASP.NET Identity ... :) verlieren.

Wenn Sie das JWT-Token nicht kennen, schauen Sie sich Folgendes an:

https://tools.ietf.org/html/rfc7519

Grundsätzlich sieht ein JWT-Token folgendermaßen aus:

<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>

Beispiel:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ

Ein JWT-Token besteht aus drei Abschnitten:

  1. Header: JSON-Format, das in Base64 codiert ist
  2. Ansprüche: JSON-Format, das in Base64 codiert ist.
  3. Signatur: Erstellt und signiert basierend auf Header und Claims, die in Base64 codiert sind.

Wenn Sie die Website jwt.io mit dem obigen Token verwenden, können Sie das Token entschlüsseln und wie folgt anzeigen:

enter image description here

Technisch verwendet JWT eine Signatur, die aus Headern und Ansprüchen mit dem in den Headern angegebenen Sicherheitsalgorithmus signiert ist (Beispiel: HMACSHA256). Daher muss JWT über HTTPs übertragen werden, wenn Sie vertrauliche Informationen in Ansprüchen speichern.

Um die JWT-Authentifizierung nutzen zu können, benötigen Sie keine OWIN-Middleware, wenn Sie über ein älteres Web-API-System verfügen. Das einfache Konzept besteht darin, wie JWT-Token bereitgestellt und das Token bei der Anforderung überprüft werden kann. Das ist es.

Zurück zur Demo, um das Gewicht des JWT-Tokens gering zu halten, speichere ich nur username und expiration time in JWT. Auf diese Weise müssen Sie jedoch eine neue lokale Identität (Prinzipal) erstellen, um weitere Informationen hinzuzufügen, z. B .: Rollen .., wenn Sie eine Rollenautorisierung durchführen möchten. Wenn Sie jedoch mehr Informationen in JWT einfügen möchten, liegt es an Ihnen: Es ist sehr flexibel.

Anstatt die OWIN-Middleware zu verwenden, können Sie einfach einen JWT-Token-Endpunkt mithilfe der Aktion auf dem Controller bereitstellen:

public class TokenController : ApiController
{
    // This is naive endpoint for demo, it should use Basic authentication
    // to provide token or POST request
    [AllowAnonymous]
    public string Get(string username, string password)
    {
        if (CheckUser(username, password))
        {
            return JwtManager.GenerateToken(username);
        }

        throw new HttpResponseException(HttpStatusCode.Unauthorized);
    }

    public bool CheckUser(string username, string password)
    {
        // should check in the database
        return true;
    }
}

Dies ist eine naive Handlung; In der Produktion sollten Sie eine POST -Anforderung oder einen Basisauthentifizierungs-Endpunkt verwenden, um das JWT-Token bereitzustellen.

Wie wird das Token basierend auf username generiert?

Sie können das NuGet-Paket mit dem Namen System.IdentityModel.Tokens.Jwt von Microsoft verwenden, um das Token zu generieren, oder sogar ein anderes Paket, wenn Sie möchten. In der Demo verwende ich HMACSHA256 mit SymmetricKey:

/// <summary>
/// Use the below code to generate symmetric Secret Key
///     var hmac = new HMACSHA256();
///     var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";

public static string GenerateToken(string username, int expireMinutes = 20)
{
    var symmetricKey = Convert.FromBase64String(Secret);
    var tokenHandler = new JwtSecurityTokenHandler();

    var now = DateTime.UtcNow;
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, username)
        }),

        Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),

        SigningCredentials = new SigningCredentials(
            new SymmetricSecurityKey(symmetricKey), 
            SecurityAlgorithms.HmacSha256Signature)
    };

    var stoken = tokenHandler.CreateToken(tokenDescriptor);
    var token = tokenHandler.WriteToken(stoken);

    return token;
}

Der Endpunkt für die Bereitstellung des JWT-Tokens ist fertig. Nun, wie kann man das JWT validieren, wenn die Anfrage eintrifft? In der Demo habe ich JwtAuthenticationAttribute erstellt, das von IAuthenticationFilter erbt (mehr Details zum Authentifizierungsfilter in hier ).

Mit diesem Attribut können Sie jede Aktion authentifizieren: Sie müssen nur dieses Attribut für diese Aktion festlegen.

public class ValueController : ApiController
{
    [JwtAuthentication]
    public string Get()
    {
        return "value";
    }
}

Sie können auch OWIN Middleware oder DelegateHander verwenden, wenn Sie alle eingehenden Anforderungen für Ihre WebAPI validieren möchten (nicht spezifisch für Controller oder Aktion).

Nachfolgend finden Sie die Kernmethode des Authentifizierungsfilters:

private static bool ValidateToken(string token, out string username)
{
    username = null;

    var simplePrinciple = JwtManager.GetPrincipal(token);
    var identity = simplePrinciple.Identity as ClaimsIdentity;

    if (identity == null)
        return false;

    if (!identity.IsAuthenticated)
        return false;

    var usernameClaim = identity.FindFirst(ClaimTypes.Name);
    username = usernameClaim?.Value;

    if (string.IsNullOrEmpty(username))
       return false;

    // More validate to check whether username exists in system

    return true;
}

protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
    string username;

    if (ValidateToken(token, out username))
    {
        // based on username to get more information from database 
        // in order to build local identity
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, username)
            // Add more claims if needed: Roles, ...
        };

        var identity = new ClaimsIdentity(claims, "Jwt");
        IPrincipal user = new ClaimsPrincipal(identity);

        return Task.FromResult(user);
    }

    return Task.FromResult<IPrincipal>(null);
}

Der Workflow besteht darin, mithilfe der JWT-Bibliothek (NuGet-Paket oben) das JWT-Token zu validieren und anschließend ClaimsPrincipal zurückzugeben. Sie können weitere Überprüfungen durchführen, z. B. überprüfen, ob ein Benutzer auf Ihrem System vorhanden ist, und bei Bedarf weitere benutzerdefinierte Überprüfungen hinzufügen. Der Code zum Überprüfen des JWT-Tokens und zum Zurückerhalten des Principals:

public static ClaimsPrincipal GetPrincipal(string token)
{
    try
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;

        if (jwtToken == null)
            return null;

        var symmetricKey = Convert.FromBase64String(Secret);

        var validationParameters = new TokenValidationParameters()
        {
            RequireExpirationTime = true,
            ValidateIssuer = false,
            ValidateAudience = false,
            IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
        };

        SecurityToken securityToken;
        var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);

        return principal;
    }
    catch (Exception)
    {
        //should write log
        return null;
    }
}

Wenn das JWT-Token validiert ist und der Principal zurückgegeben wird, sollten Sie eine neue lokale Identität erstellen und weitere Informationen hinzufügen, um die Rollenberechtigung zu überprüfen.

Denken Sie daran, config.Filters.Add(new AuthorizeAttribute()); (Standardautorisierung) im globalen Bereich hinzuzufügen, um anonyme Anforderungen an Ihre Ressourcen zu verhindern.

Sie können Postman verwenden, um die Demo zu testen:

Token anfordern (naiv, wie oben erwähnt, nur für die Demo):

GET http://localhost:{port}/api/token?username=cuong&password=1

Fügen Sie das JWT-Token in den Header für die autorisierte Anforderung ein. Beispiel:

GET http://localhost:{port}/api/value

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s

Die Demo finden Sie hier: https://github.com/cuongle/WebApi.Jwt

526
cuongle

Ich habe es mit minimalem Aufwand geschafft (genauso einfach wie mit ASP.NET Core).

Dafür benutze ich OWIN Startup.cs file und Microsoft.Owin.Security.Jwt library.

Damit die App Startup.cs trifft, müssen wir Web.config ändern:

<configuration>
  <appSettings>
    <add key="owin:AutomaticAppStartup" value="true" />
    ...

So sollte Startup.cs aussehen:

using MyApp.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;

[Assembly: OwinStartup(typeof(MyApp.App_Start.Startup))]

namespace MyApp.App_Start
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseJwtBearerAuthentication(
                new JwtBearerAuthenticationOptions
                {
                    AuthenticationMode = AuthenticationMode.Active,
                    TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidAudience = ConfigHelper.GetAudience(),
                        ValidIssuer = ConfigHelper.GetIssuer(),
                        IssuerSigningKey = ConfigHelper.GetSymmetricSecurityKey(),
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true
                    }
                });
        }
    }
}

Viele von euch verwenden heutzutage ASP.NET Core. Wie ihr sehen könnt, unterscheidet es sich nicht sehr von dem, was wir dort haben.

Zuerst war ich wirklich ratlos, ich habe versucht, benutzerdefinierte Anbieter usw. zu implementieren. Aber ich hatte nicht erwartet, dass es so einfach sein würde. OWIN rockt einfach!

Nur eine Sache zu erwähnen - nachdem ich OWIN Startup NSWag aktiviert habe, funktioniert die Bibliothek nicht mehr für mich (zum Beispiel möchten einige von Ihnen möglicherweise TypeScript-HTTP-Proxys für Angular App automatisch generieren).

Die Lösung war auch sehr einfach - ich ersetzte NSWag durch Swashbuckle und hatte keine weiteren Probleme.


Ok, teile jetzt den ConfigHelper Code:

public class ConfigHelper
{
    public static string GetIssuer()
    {
        string result = System.Configuration.ConfigurationManager.AppSettings["Issuer"];
        return result;
    }

    public static string GetAudience()
    {
        string result = System.Configuration.ConfigurationManager.AppSettings["Audience"];
        return result;
    }

    public static SigningCredentials GetSigningCredentials()
    {
        var result = new SigningCredentials(GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256);
        return result;
    }

    public static string GetSecurityKey()
    {
        string result = System.Configuration.ConfigurationManager.AppSettings["SecurityKey"];
        return result;
    }

    public static byte[] GetSymmetricSecurityKeyAsBytes()
    {
        var issuerSigningKey = GetSecurityKey();
        byte[] data = Encoding.UTF8.GetBytes(issuerSigningKey);
        return data;
    }

    public static SymmetricSecurityKey GetSymmetricSecurityKey()
    {
        byte[] data = GetSymmetricSecurityKeyAsBytes();
        var result = new SymmetricSecurityKey(data);
        return result;
    }

    public static string GetCorsOrigins()
    {
        string result = System.Configuration.ConfigurationManager.AppSettings["CorsOrigins"];
        return result;
    }
}

Ein weiterer wichtiger Aspekt: ​​Ich habe das JWT-Token über den Header Authorization gesendet. Daher sieht der TypeScript-Code für mich folgendermaßen aus:

(Der folgende Code wird generiert von NSWag )

@Injectable()
export class TeamsServiceProxy {
    private http: HttpClient;
    private baseUrl: string;
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

    constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
        this.http = http;
        this.baseUrl = baseUrl ? baseUrl : "https://localhost:44384";
    }

    add(input: TeamDto | null): Observable<boolean> {
        let url_ = this.baseUrl + "/api/Teams/Add";
        url_ = url_.replace(/[?&]$/, "");

        const content_ = JSON.stringify(input);

        let options_ : any = {
            body: content_,
            observe: "response",
            responseType: "blob",
            headers: new HttpHeaders({
                "Content-Type": "application/json", 
                "Accept": "application/json",
                "Authorization": "Bearer " + localStorage.getItem('token')
            })
        };

Siehe Header Teil - "Authorization": "Bearer " + localStorage.getItem('token')

7
Alex Herman

Hier ist eine sehr minimale und sichere Implementierung einer anspruchsbasierten Authentifizierung mithilfe von JWT-Token in einer ASP.NET Core-Web-API.

zunächst müssen Sie einen Endpunkt verfügbar machen, der ein JWT-Token mit Ansprüchen zurückgibt, die einem Benutzer zugewiesen sind:

 /// <summary>
        /// Login provides API to verify user and returns authentication token.
        /// API Path:  api/account/login
        /// </summary>
        /// <param name="paramUser">Username and Password</param>
        /// <returns>{Token: [Token] }</returns>
        [HttpPost("login")]
        [AllowAnonymous]
        public async Task<IActionResult> Login([FromBody] UserRequestVM paramUser, CancellationToken ct)
        {

            var result = await UserApplication.PasswordSignInAsync(paramUser.Email, paramUser.Password, false, lockoutOnFailure: false);

            if (result.Succeeded)
            {
                UserRequestVM request = new UserRequestVM();
                request.Email = paramUser.Email;


                ApplicationUser UserDetails = await this.GetUserByEmail(request);
                List<ApplicationClaim> UserClaims = await this.ClaimApplication.GetListByUser(UserDetails);

                var Claims = new ClaimsIdentity(new Claim[]
                                {
                                    new Claim(JwtRegisteredClaimNames.Sub, paramUser.Email.ToString()),
                                    new Claim(UserId, UserDetails.UserId.ToString())
                                });


                //Adding UserClaims to JWT claims
                foreach (var item in UserClaims)
                {
                    Claims.AddClaim(new Claim(item.ClaimCode, string.Empty));
                }

                var tokenHandler = new JwtSecurityTokenHandler();
                  // this information will be retrived from you Configuration
                //I have injected Configuration provider service into my controller
                var encryptionkey = Configuration["Jwt:Encryptionkey"];
                var key = Encoding.ASCII.GetBytes(encryptionkey);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Issuer = Configuration["Jwt:Issuer"],
                    Subject = Claims,

                // this information will be retrived from you Configuration
                //I have injected Configuration provider service into my controller
                    Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Configuration["Jwt:ExpiryTimeInMinutes"])),

                    //algorithm to sign the token
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)

                };

                var token = tokenHandler.CreateToken(tokenDescriptor);
                var tokenString = tokenHandler.WriteToken(token);

                return Ok(new
                {
                    token = tokenString
                });
            }

            return BadRequest("Wrong Username or password");
        }

jetzt müssen Sie Ihren Diensten in Ihrem ConfigureServices in Ihrem startup.cs Authentifizierung hinzufügen, um die JWT-Authentifizierung wie folgt als Standardauthentifizierungsdienst hinzuzufügen:

services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
             .AddJwtBearer(cfg =>
             {
                 cfg.RequireHttpsMetadata = false;
                 cfg.SaveToken = true;
                 cfg.TokenValidationParameters = new TokenValidationParameters()
                 {
                     //ValidateIssuerSigningKey = true,
                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Encryptionkey"])),
                     ValidateAudience = false,
                     ValidateLifetime = true,
                     ValidIssuer = configuration["Jwt:Issuer"],
                     //ValidAudience = Configuration["Jwt:Audience"],
                     //IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"])),
                 };
             });

jetzt können Sie Ihren Autorisierungsdiensten Richtlinien wie folgt hinzufügen:

services.AddAuthorization(options =>
            {
                options.AddPolicy("YourPolicyNameHere",
                                policy => policy.RequireClaim("YourClaimNameHere"));
            });

ALTERNATIV, Sie können auch (nicht erforderlich) alle Ihre Ansprüche aus Ihrer Datenbank ausfüllen, da dies beim Start der Anwendung nur einmal ausgeführt wird und sie Richtlinien wie diesen hinzufügt:

  services.AddAuthorization(async options =>
            {
                var ClaimList = await claimApplication.GetList(applicationClaim);
                foreach (var item in ClaimList)
                {                        
                    options.AddPolicy(item.ClaimCode, policy => policy.RequireClaim(item.ClaimCode));                       
                }
            });

jetzt können Sie den Richtlinienfilter auf eine der Methoden setzen, die Sie autorisieren möchten:

 [HttpPost("update")]
        [Authorize(Policy = "ACC_UP")]
        public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
        {
//your logic goes here
}

Hoffe das hilft

3
Zeeshan Adil

Ich denke, Sie sollten einen 3D-Party-Server verwenden, um das JWT-Token zu unterstützen, und es gibt keine sofort einsatzbereite JWT-Unterstützung in WEB API 2.

Es gibt jedoch ein OWIN-Projekt zur Unterstützung eines Formats von signiertem Token (nicht JWT). Es funktioniert als reduziertes OAuth-Protokoll, um nur eine einfache Form der Authentifizierung für eine Website bereitzustellen.

Sie können mehr darüber lesen, z. hier .

Es ist ziemlich lang, aber die meisten Teile sind Details mit Controllern und ASP.NET-Identität, die Sie möglicherweise gar nicht benötigen. Am wichtigsten sind

Schritt 9: Unterstützung für OAuth Bearer Tokens Generation hinzufügen

Schritt 12: Testen der Back-End-API

Dort können Sie nachlesen, wie Sie den Endpunkt (z. B. "/ token") einrichten, auf den Sie über das Frontend zugreifen können (und Details zum Format der Anforderung).

Weitere Schritte enthalten Details zum Verbinden dieses Endpunkts mit der Datenbank usw., und Sie können die gewünschten Teile auswählen.

1