paint-brush
ASP.NET Core projenizde neden JWT'ye ihtiyacınız var?ile@igorlopushko
7,818 okumalar
7,818 okumalar

ASP.NET Core projenizde neden JWT'ye ihtiyacınız var?

ile Igor Lopushko16m2024/02/13
Read on Terminal Reader

Çok uzun; Okumak

Hikaye, JWT oluşturmak için bir Web API'sinin nasıl oluşturulacağı ve daha sonra bunun CRUD Web API'sinde yetkilendirme için nasıl kullanılacağı hakkındadır.
featured image - ASP.NET Core projenizde neden JWT'ye ihtiyacınız var?
Igor Lopushko HackerNoon profile picture
0-item

JWT, JSON Web Token anlamına gelir ve kimlik doğrulama değil, bir yetkilendirme mekanizmasıdır. Şimdi bu ikisi arasındaki farkın ne olduğunu bulalım.


Kimlik doğrulama, kullanıcının tam olarak iddia ettiği kişi olduğunun doğrulanmasını sağlayan mekanizmadır. Kullanıcının kullanıcı adı ve şifre sağladığı ve sistemin bunları doğruladığı bir oturum açma işlemidir. Kimlik doğrulama şu soruyu yanıtlıyor: Kullanıcı kim?


Yetkilendirme , kullanıcının belirli bir kaynağa hangi erişim haklarına sahip olduğunun doğrulanmasını sağlayan mekanizmadır. Kullanıcılara bazı roller ve belirli bir rolün sahip olduğu bir dizi izin verme sürecidir. Yetkilendirme şu soruyu yanıtlıyor: Kullanıcı sistemde hangi haklara sahiptir?

Kimlik Doğrulama ve Yetkilendirme


Kimlik Doğrulamanın her zaman önce geldiğini ve Yetkilendirmenin ikinci sırada geldiğini anlamak önemlidir. Yani kimliğinizi doğrulamadan izin alamazsınız. Peki en popüler yetkilendirme yöntemleri nelerdir? Web uygulaması için yetkilendirmeyi ele almada iki ana yaklaşım vardır.

Oturumlar

Kullanıcıların yetkilendirilmesi için web üzerinde geleneksel bir yaklaşım, çerez tabanlı sunucu tarafı oturumudur. İşlem, bir kullanıcı oturum açtığında ve bir sunucu onun kimliğini doğruladığında başlar. Bundan sonra sunucu, Oturum Kimliğiyle bir oturum oluşturur ve bunu sunucunun belleğinde bir yerde saklar. Sunucu, Oturum Kimliğini istemciye geri gönderir ve istemci, Oturum Kimliğini çerezlerde saklar. Her istek için istemci, isteğin bir parçası olarak bir Oturum Kimliği gönderir ve sunucu, hafızasındaki Oturum Kimliğini ve kullanıcının bu oturumla ilgili izinlerini doğrular.

Oturum bazlı yetkilendirme

Jetonlar

Bir diğer popüler yaklaşım ise yetkilendirme için token kullanmaktır. İşlem, kullanıcı oturum açma adını ve parolalarını girdiğinde ve istemci sunucuya oturum açma isteği gönderdiğinde benzer şekilde başlar. Sunucu, bir oturum oluşturmak yerine gizli belirteçle imzalanmış bir belirteç oluşturur. Daha sonra sunucu, belirteci istemciye geri gönderir ve istemcinin bunu yerel bir depolama biriminde saklaması gerekir. Oturum tabanlı yaklaşıma benzer şekilde, istemcinin her istek için sunucuya bir belirteç göndermesi gerekir. Ancak sunucu, kullanıcı oturumuna ilişkin herhangi bir ek bilgi saklamaz. Sunucunun, jetonun oluşturulduğundan ve gizli anahtarla imzalandığından beri değişmediğini doğrulaması gerekir.

Belirteç tabanlı yetkilendirme

Oturum ve Jeton Karşılaştırması

Oturum tabanlı yetkilendirme yaklaşımı, Siteler Arası İstek Sahteciliği (CSRF) olarak bilinen bir saldırıya karşı savunmasız olabilir. Saldırganın, ödeme yapmak veya şifre değiştirmek gibi amaçlamadığı eylemleri gerçekleştirmek için oturum açtığı bir siteyi işaret etmesi bir tür saldırıdır.


Başka bir şey, oturum tabanlı yetkilendirme yaklaşımı kullanıldığında, istemci ile sunucu arasında durum bilgisi olan bir oturum oluşturulmasıdır. Sorun, bir istemcinin aynı uygulama kapsamındaki farklı sunuculara erişmek istemesi durumunda, bu sunucuların bir oturum durumunu paylaşmak zorunda olmasıdır. Başka bir durumda, oturum farklı olacağından istemcinin her sunucuda yetkilendirilmesi gerekecektir.

Oturum tabanlı yetkilendirme durumu paylaşımı


Öte yandan token tabanlı yetkilendirme yaklaşımı, oturum verilerinin sunucu tarafında saklanmasını gerektirmez ve birden fazla sunucu arasındaki yetkilendirmeyi kolaylaştırabilir.


Ancak tokenlar yine de bir saldırgan tarafından çalınabilir ve tokenları geçersiz kılmak da zor olabilir. Bu makalede ayrıntıları ve geçersiz kılmanın nasıl ele alınacağını daha ayrıntılı olarak göreceğiz.

JWT

JSON Web Token (JWT), taraflar arasında bilgilerin bir JSON nesnesi olarak güvenli bir şekilde iletilmesi için kompakt ve bağımsız bir yol tanımlayan açık bir standarttır. Bu bilgiler dijital olarak imzalandığı için doğrulanabilir ve güvenilir olabilir. JWT'ler bir sır ( HMAC algoritmasıyla) veya RSA veya ECDSA kullanılarak genel/özel anahtar çifti kullanılarak imzalanabilir.

JWT yapısı

JSON Web Tokenları noktalarla ayrılmış üç bölümden oluşur .


  • Başlık
 { "alg": "HS256", "typ": "JWT" }

Başlık genellikle iki bölümden oluşur: belirtecin türü ve kullanılan imzalama algoritması.


  • Yük
 { "sub": "1234567890", "name": "John Doe", "admin": true }

Yük, kullanıcı hakkındaki ifadeler olan talepleri içerir. Yük daha sonra JSON Web Token'ın ikinci bölümünü oluşturmak için Base64Url olarak kodlanır. Talep olarak kullanılan standart alanların açıklamasını burada bulabilirsiniz.


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

İmza kısmını oluşturmak için kodlanmış başlığı, kodlanmış veriyi, bir sırrı ve başlıkta belirtilen algoritmayı alıp imzalamanız gerekiyor.


Belirteç genellikle aşağıdaki gibi görünür:

xxxxx.yyyyy.zzzzz


jwt.io'ya gidebilir ve örnek bir jetonun veya kendinizinkinin hatalarını ayıklayabilirsiniz. Belirtecinizi Kodlanmış alana yapıştırın ve belirteç imzasının Algoritmasını seçin.

jwt.io hata ayıklayıcısı

.NET projesi

Artık JWT'nin nasıl çalıştığına dair teorik bilgiye sahip olduğumuza göre, bunu gerçek hayattaki projelere uygulayabiliriz. Kahve varlığı için CRUD işlemlerini temsil eden basit bir API'ye sahip olduğumuzu varsayalım. Coffee API'yi temsil eden bir ASP.NET Core API projesi oluşturacağız. Bundan sonra JWT oluşturabilecek bir Identity API'yi temsil edecek başka bir ASP.NET Core API projesi oluşturacağız. Gerçek hayatta, Kimlik Doğrulama/Yetkilendirme amacıyla muhtemelen Identity Server veya Okta veya Auth0 kullanırsınız. Ancak JWT'nin nasıl oluşturulacağını göstermek için kendi Kimlik API'mizi oluşturacağız. Identity API tamamlandığında, denetleyicisini çağırabilir ve kullanıcının verilerine göre JWT oluşturabiliriz. Ayrıca Coffee API'yi, her istekte JWT'nin geçirilmesini gerektiren bir yetkilendirme yapılandırmasıyla koruyabiliriz.

.NET proje ortamı

Kahve API'si

Öncelikle Coffee API'yi temsil eden basit bir ASP.NET Core API projesi oluşturacağız. İşte bu projenin yapısı:

Kahve API'si - Proje yapısı


Model klasöründeki Coffee.cs ile başlayalım. Id ve Name özelliklerine sahip basit bir varlıktır.

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


API ile çalışırken varlıklarımızı saklamamız gerekiyor. Öyleyse basit bir bellek içi depolamayı tanıtalım. Data klasöründeki Storage.cs dosyasında bulunur.

 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; } }


Coffee API'ye gelen istekleri temsil edecek bir sınıfa ihtiyacımız var. Şimdi Contracts klasöründe CoffeeRequest.cs dosyasını oluşturalım.

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


Tamamlandığında, CoffeeController.cs kahve varlığı için CRUD işlemlerini temsil eden Controller klasörüne uygulayabiliriz.

 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(); } }


Coffee API tamamlandı ve projeyi çalıştırıp Swagger UI'yi aşağıdaki gibi görebiliriz:

Kahve API'si - Swagger Kullanıcı Arayüzü

Kimlik API'si

Identity API'yi temsil eden başka bir ASP.NET Core API projesi oluşturalım. İşte bu projenin yapısı:

Kimlik API'si - Proje yapısı

Email ve Password özelliklerine sahip yeni bir JWT oluşturma isteğini temsil eden Contracts klasöründeki TokenGenerationRequest.cs ile başlayalım.

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


Yalnızca JWT oluşturma mantığını temsil eden TokenController.cs uygulamamız gerekiyor. Ancak bunu yapmadan önce Microsoft.AspNetCore.Authentication.JwtBearer NuGet paketinin kurulması gerekiyor.

 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); } }


SecretKey , Issuer ve Audience gibi hassas yapıların yapılandırmada bir yere yerleştirilmesi gerektiğini unutmayın. Sadece bu test projesini basitleştirmek için sabit kodlanmıştır. Lifetime alanı 20 dakikaya ayarlanmıştır, bu da belirtecin o süre boyunca geçerli olacağı anlamına gelir. Bu parametreyi de yapılandırabilirsiniz.


Artık projeyi çalıştırıp Swagger kullanıcı arayüzünü aşağıdaki gibi görebiliriz:

Kimlik API'si - Swagger Kullanıcı Arayüzü


/token uç noktasına bir çağrı yapalım ve yeni bir JWT oluşturalım. Aşağıdaki yükü deneyin:

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


Kimlik API'si karşılık gelen JWT'yi oluşturacaktır:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM

Coffee API'de Yetkilendirmeyi Etkinleştirme

Artık Identity API hazır olduğunda ve bize token sağladığında Coffee API'yi yetkilendirme ile koruyabiliriz. Yine Microsoft.AspNetCore.Authentication.JwtBearer NuGet paketinin kurulması gerekiyor.


Gerekli hizmetleri kimlik doğrulama hizmetleriyle kaydetmemiz gerekiyor. Bir oluşturucu oluşturduktan hemen sonra Program.cs dosyasına aşağıdaki kodu ekleyin.

 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();


Ara katman yazılımındaki sıranın önemli olduğunu hatırlamak önemlidir. AddAuthentication() yöntemini çağırıp JwtBearerDefaults.AuthenticationScheme kimlik doğrulama şeması olarak belirterek kimlik doğrulamayı etkinleştiriyoruz. Bearer değeri içeren bir sabittir.

 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"; } }


Yetkilendirme sırasında JWT'nin hangi parametrelerinin doğrulanacağını açıklayan TokenValidationParameters belirtmemiz gerekiyor. Ayrıca JWT imzasını doğrulamak için Identity API'dekisigningCredentials'a signingCredentials IssuerSigningKey belirtiriz. TokenValidationParameters hakkında daha fazla ayrıntıyı buradan kontrol edin.


Bir sonraki kod parçası, oluşturucuya kimlik doğrulama ve yetkilendirme yeteneklerini etkinleştiren ara yazılım ekler. UseHttpsRedirection() ve MapControllers() yöntemleri arasına eklenmelidir.

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


Artık Controller veya eylemleri üzerinde Authorize niteliğini kullanabiliriz. Bu kodun uygulanmasıyla artık CoffeeController tüm eylemler bir yetkilendirme mekanizmasıyla korunuyor ve isteğin bir parçası olarak JWT'nin gönderilmesi gerekiyor.

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


Coffee API'nin herhangi bir uç noktasına çağrı yaparsak, HttpContext.User hata ayıklayabilir ve JWT'de belirttiğimiz iddialarla doldurulduğunu ve bir Identity sahip olduğunu görebiliriz. ASP.NET Core'un Yetkilendirmeyi nasıl ele aldığını anlamak önemli bir şeydir.

Coffee API - Talepler JWT'den doldurulur

Swagger Kullanıcı Arayüzüne Yetki Ekleme

Yetkilendirme ile Coffee API'yi korumak için harika çalışmalar yaptık. Ancak Coffee API projesini çalıştırırsanız ve Swagger UI'yi açarsanız isteğin bir parçası olarak JWT gönderemezsiniz. Bunu düzeltmek için Program.cs dosyasını aşağıdaki kodla güncellememiz gerekiyor:

 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[]{} } }); });


Bundan sonra sağ üst köşede Yetkilendir butonunu görebileceğiz:

Coffee API - Yetkilendir düğmesi göründü


Yetkilendir butonuna tıkladığınızda JWT'ye aşağıdaki gibi girebileceksiniz:

Kahve API'si - JWT değerini girin

Test için Postman'ı kullanın

Kendinizi Swagger UI'yi kullanmakla sınırlayamazsınız ve API'nin testini Postman aracı aracılığıyla gerçekleştirebilirsiniz. Önce Identity API'nin /token uç noktasını çağıralım. Payload olarak JSON kullanacağımız için Headers kısmında Content-Type başlığını application/json değeriyle belirtmemiz gerekiyor.

Kimlik API'si - Başlıkları belirtin


Bundan sonra /token uç noktasını çağırabilir ve yeni bir JWT alabiliriz.

Kimlik API'si - JWT Oluştur


Artık JWT'yi kopyalayıp Coffee API'yi çağırmak için kullanabiliriz. Uç noktaları test etmek, oluşturmak ve güncellemek istiyorsak Identity API'ye benzer Content-Type başlığını belirtmemiz gerekir. Authorization başlığının ayrıca Bearer [your JWT value] değeriyle ayarlanması gerekir. Bundan sonra Gönder düğmesine basın ve sonucu görün.

Coffee API - Tüm varlıkları alın

Rol bazlı yetkilendirme

Hatırlayacağınız gibi JWT'nin yük kısmı, tam olarak anahtar/değer çiftleri olan değerlere sahip bir dizi talepten oluşur. Rol tabanlı yetkilendirme, kullanıcının ait olduğu role bağlı olarak uygulama kaynaklarına erişimi farklılaştırmanıza olanak tanır.


Identity API'deki TokenController.cs dosyasındaki Create() metodunu, role yeni bir talep ekleyen kodla güncellersek; Coffee API'de rol tabanlı kimlik doğrulamayı gerçekleştirebiliriz. ClaimTypes.Role , rol talebinin önceden tanımlanmış adıdır.

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


Rol adını belirterek CoffeeController.cs dosyasındaki Authorize özelliğini güncelleyin:

 [Authorize(Roles = "Barista")]


Artık Coffee API'ye çağrı yapan tüm kullanıcıların Barista değerine sahip rol talebinde bulunması gerekiyor. Aksi takdirde 403 Forbidden durum kodu alacaklardır.

Talebe dayalı yetkilendirme

Authorize özelliği, rol tabanlı kimlik doğrulamayı kolayca işleyebilir. Peki ya bu yeterli değilse ve erişimi yaş gibi bazı kullanıcı özelliklerine göre farklılaştırmak istiyorsak? Muhtemelen taleplerinizi JWT'ye ekleyebileceğinizi ve bunları yetkilendirme mantığı oluşturmak için kullanabileceğinizi tahmin etmişsinizdir. Rol tabanlı yetkilendirmenin kendisi, talep tabanlı yetkilendirmenin özel bir durumudur; tıpkı rolün önceden tanımlanmış türdeki aynı talep nesnesi olması gibi.


Identity API'deki TokenController.cs dosyasındaki Create() yöntemini yeni bir iddia ekleyen IsGourmet koduyla güncelleyelim.

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


Coffee API'deki Program.cs dosyasında bir iddiayı doğrulayan ve Authorize özelliğinde kullanılabilecek bir politika oluşturmamız gerekiyor. AddAuthentication() yöntem çağrısından hemen sonra aşağıdaki kodun eklenmesi gerekir.

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


İlke adını belirterek CoffeeController.cs dosyasındaki Authorize özniteliğini güncelleyin:

 [Authorize(Policy = "OnlyForGourmet")]

Özet

Tebrikler! .NET'te JWT'yi öğrenmek için büyük çaba harcadınız. Artık JWT ilkelerini ve .NET uygulamalarında yetkilendirme gerçekleştirmek için onu kullanmanın neden önemli olduğunu iyice anlamış olmanız gerekir. Ancak ASP.NET Core uygulamalarında kimlik doğrulama ve yetkilendirme alanında henüz yeni bir başlangıç yaptık.


Bu makalede tartıştığımız konularla ilgili Microsoft belgelerine bakmanızı öneririm. .NET platformunda yetkilendirme ve rol yönetimi için de birçok yerleşik yetenek bulunmaktadır. Yetkilendirmeyle ilgili Microsoft belgeleri bu makaleye iyi bir katkı olabilir.