Autoryzacja przy użyciu JWT tokena w ASP .NET Core

środa, 5 grudnia 2018 16:36

Autoryzacja to część praktycznie każdej aplikacji internetowej.

W dzisiejszym wpisie chciałbym zademonstrować, jak w prosty sposób zaimplementować prostą autoryzację opartą o JSON Web Token.

JWT jest bazującym na JSON-ie standardem do tworzenia oraz odczytywania tokenów, które zawierają informacje o kontekście użytkownika, rolach itp. Za pomocą JWT możemy zautoryzować się z serwerem za pomocą loginu i hasła, a w efekcie otrzymać token, który później zostaje umieszczany w nagłówku zapytania do API.

Przykład działania

Implementacja JSON Web Token w .NET Core

Jeżeli chodzi o .NET Core, to biblioteki domyślnie umożliwiają w prosty sposób obsługę tego typu autoryzacji. W poniższym przykładzie zademonstruje aplikację, która wykorzystuje dotnet core w wersji 2.1.5.

Przejdźmy do kodu.

Utwórz projekt odraz dodaj do niego aplikację typu web api

dotnet new sln -n JWTExample
dotnet new webapi -n JWTExample.Web -o Web/JWTExample.Web
dotnet sln JWTExample.sln add Web\JWTExample.Web\JWTExample.Web.csproj

Stwórz odpowiednie klasy

Na początek stwórz klasę JWTConfiguration, która będzie zawierać ustawienia, klasę UserIdentity będącą modelem dla uprawnień oraz AccessToken zwracany przez serwis. Ja te pliki stworzyłem w katalogu Models.

  • Struktura JWTConfiguration

    public class JWTConfiguration
    {
        public string SecretKey { get; set; }
        public string ValidIssuer { get; set; }
        public string ValidAudience { get; set; }
        public int TokenExpirationTime { get; set; }
    }
  • Struktura UserIdenity

    public class UserIdentity
    {
        public string Login { get; set; }
        public string Password { get; set; }
    }
  • Struktura AccessToken

    public class AccessToken
    {
        public DateTime ExpireOnDate { get; set; }
        public long ExpiryIn { get; set; }
        public string Token { get; set; }
        public bool Success { get; set; }
    }

W pliku appsettings.json odpowiednio dodaj konfigurację

{
    "Logging": {
    "LogLevel": {
        "Default": "Warning"
    }
    },
    "AllowedHosts": "*",
    "JwtConfiguration": {
    "SecretKey": "B61D7543-8584-43B7-AV3B2-B319219AF0DA", // some example secret key
    "ValidIssuer": "C5D3A460-AF5F-33FF-9600-300460774FF9", //some example valid issuer
    "ValidAudience": "621B62B0-83C2-KK57-BEAF-9362D3D31EC5", // some example audience
    "TokenExpirationTime": "15" // expiration token time in minutes
    },
    "UserIdentity": {
        "Login": "developerlife",
        "Password": "7c7d6a040a402039473269df719e06b1" // hashed password with md5 - aziemianski
    }
}

Przejdź do pliku Startup.cs i odpowiednio załaduj konfigurację do service providera

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient((config) =>
    {
        var conf = new JWTConfiguration();
        Configuration.GetSection("JWTConfiguration").Bind(conf);
        return conf;
    });
    services.AddTransient((config) =>
    {
        var conf = new UserIdentity();
        Configuration.GetSection("UserIdentity").Bind(conf);
        return conf;
    });
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Skonfiguruj JWT jak poniżej

  1. Utwórz metodę ConfigureJWT

    public void ConfigureJwt(IServiceCollection services)
    {
        var config = services.BuildServiceProvider().GetService<JWTConfiguration>;();
        var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SecretKey));
        var tokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKey = signingKey,
            ValidIssuer = config.ValidIssuer,
            ValidAudience = config.ValidAudience
        };
        services.AddAuthentication(o =>
        {
            o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(c =>
        {
            c.RequireHttpsMetadata = false;
            c.SaveToken = true;
            c.TokenValidationParameters = tokenValidationParameters;
        });
    }
  2. Wywołaj ją na końcu metody ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        ConfigureJwt(services); // there is new line
    }
  3. Zmodyfikuj metodę Configure

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
        app.UseAuthentication(); // there is new line
        app.UseHttpsRedirection();
        app.UseMvc();
    }

Przygotuj API do generowania tokenu

  1. W katalogu Controllers dodaj plik o nazwie AuthController.cs

  2. Dodaj metodę typu POST o nazwie GenerateToken przyjmującą jako argument UserIdentity i zwracającą AccessToken. Atrybut Route ustaw na wartość „token”. W konstruktorze kontrolera wstrzyknij UserIdentity oraz JWTConfiguration.

    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly UserIdentity userIdentity;
        private readonly JWTConfiguration configuration;
    
        public AuthController(UserIdentity userIdentity, JWTConfiguration configuration)
        {
            this.userIdentity = userIdentity;
            this.configuration = configuration;
        }
    
        [HttpPost]
        [Route("token")]
        public AccessToken GenerateToken([FromBody]UserIdentity credentials)
        {
    
        }
    }
  3. Przygotuj metodę do haszowania hasła

    private static string GetMd5Hash(MD5 md5Hash, string input)
    {
        byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
        StringBuilder sBuilder = new StringBuilder();
        for (int i = 0; i < data.Length; i++)
        {
            sBuilder.Append(data[i].ToString("x2"));
        }
        return sBuilder.ToString();
    }
  4. Zaimplementuj wcześniej utworzoną metodę

    [HttpPost]
    [Route("token")]
    public AccessToken GenerateToken([FromBody]UserIdentity credentials)
    {
        string md5Password = string.Empty;
        using (MD5 md5Hash = MD5.Create())
        {
            md5Password = GetMd5Hash(md5Hash, credentials.Password);
        }
    
        if (userIdentity.Password != md5Password || userIdentity.Login != credentials.Login)
        {
            Response.StatusCode = StatusCodes.Status401Unauthorized;
            return new AccessToken { Success = false };
        }
    
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, credentials.Login),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };
    
        var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration.SecretKey));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var expiredOn = DateTime.Now.AddMinutes(configuration.TokenExpirationTime);
        var token = new JwtSecurityToken(configuration.ValidIssuer,
            configuration.ValidAudience,
            claims,
            expires: expiredOn,
            signingCredentials: creds);
    
        return new AccessToken
        {
            ExpireOnDate = token.ValidTo,
            Success = true,
            ExpiryIn = configuration.TokenExpirationTime,
            Token = new JwtSecurityTokenHandler().WriteToken(token)
        };
    }

Gratuluję. Właśnie utworzyłeś API do autoryzacji.

Teraz dodajmy kontroler o nazwie TodoController, za pomocą którego przetestujemy działanie rozwiązania.

[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
    [Authorize]
    public IList<string>GetValues()
    {
        var someClaim = this.HttpContext.User.Claims.FirstOrDefault();
        return new List<string>
        {
            "Example of JWT auth for https://developerlife.pl",
            $"User: {someClaim.Value}"
        };
    }
}

Testowanie aplikacji

Do testowania wykorzystamy narzędzie o nazwie Postman. Na początek spróbujemy wywołać zasób nie podając w headerze tokena.

Uzyskaliśmy spodziewany efekt – brak autoryzacji. Teraz wygenerujmy nasz token

Zmodyfikuj pierwsze zapytanie dodając w nagłówku otrzymany token przed jego wartością dopisując Bearer

W odpowiedzi otrzymałeś to czego oczekiwałeś

Brawo! Właśnie stworzyłeś API za pomocą .NET Core i języka C#,

które do autoryzacji wykorzystuje standard JSON Web Token. W tym momencie jesteś w stanie połączyć się ze swoją webową aplikacją w bezpieczny sposób poprzez klienta webowego, aplikację mobilną, IoT lub z czego tylko zechcesz.

Mam nadzieję, że artykuł był przejrzysty oraz zrozumiały i dzięki niemu zdobyłeś podstawową wiedzę na temat implementacji autoryzacji przy pomocy tokena. Cały kod dostępny jest tutaj.

Zostaw odpowiedź

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Komentarze: 0