İçeriğe geç

JWT (JSON Web Tokens) Nedir? .Net Core ile bir Örnek

“JWT (JSON Web Tokens), iletişim yapan birimler arasındaki veri alışverişinin güvenli bir şekilde sağlanması için bir JSON nesnesi -token- kullanarak, daha kompakt ve bilginin kendini betimlediği bir yol sunan endüstri standartıdır.” diye bir tanımla giriş yapmayacağım tabii ki 🙂

Açık konuşmak gerekirse, çekindiğim, ancak üzerine gitmem gereken konulardan birisi de, mikroservislerde kimliklendirme(authentication) ve yetkilendirme(authorization) konularıydı. 🙂 Özellikle servis odaklı yaklaşımlarda, güvenlik karın ağrısı yaratan boyutlara gelebiliyor. 

İş yerimizdeki Rest tabanlı servislerin yetkilendirilme işlemleri ile ilgili testler ve araştırmalar yaparken, güzel bir standart olan JWT’den örnekle, sizlere biraz bahsetmek istedim.

Haberleşen iki veya daha fazla sistem (Web, Mobile, IOT, Cloud vb.) arasında kullanıcı doğrulama, kullanıcı tanıma, veri bütünlüğü ve bilgi güvenliğini koruma gibi noktalarda kullanılır.

Kullanım senaryosunun akış diagramı aşağıdaki gibidir;

kuthaygumus.com-jwtworkflow

JWT’nin Çalışma Mantığı

Senaryo aslında basit. Rest tabanlı bir servisimiz veya başka bir web uygulamamız var, buradaki bir veya birden fazla operasyonu kullanmak isteyen bir de kullanıcımız olsun. Kullanıcı hizmeti almadan önce, password ve username bilgileri ile doğrulanmakta yani kimliklendirme (authentication) yapılmakta, -bu işlem de ayrı büyük bir iştir bu arada- Aşağıda yazacağımız örnek demomuzda bu doğrulama kısmı sürekli true dönen dummy bir yapıda olacak. Sonrasında doğrulama başarılı olursa, servis tarafında bir “Token” üretilecek.

Token üretimi, doğrulamayı geçen kullanıcının sonraki talepleri için önemlidir. Bu bilet için biçilen yaşam süresi sonlanana kadar istemciden servis tarafına gönderilmesi gerekmektedir. Yaşam süresi dolduktan sonra kullanıcı aynı bilet ile içeriye giremez ve HTTP 401 (unauthorized) hatası alır. Bu sebeple yeni bir giriş bileti yani Token almalıdır 🙂 Kabaca işleyiş senaryosu budur.

JWT’nin Yapısı

JWT birbirine nokta (.) ile bağlanmış, Base64 biçiminde kodlanmış 3 ayrı JSON parçasından oluşmaktadır. Bu üç parça bir araya gelir ve JWT’yi temsil eder.
Bu üç kısım sırası ile;

  • Header (Başlık)
    • Token tipi ve şifreleme algoritması bilgileri tutulur.
  • Payload (Yük)
    • Claimler kullanıcı hakkında bilgi sunan ifadelerdir.
  • Signature (İmza)
    • Signature kısmının oluşturulabilmesi için Base64 ile kodlanmış, header ve payload, gizli bir kelime yani secret ve header’da tanımlanan algoritma gereklidir.
  • Böylece tipik bir JWT aşağıdaki gibi görünür.
    • xxxxxxxxxxxxx.yyyyyyyyyyyy.zzzzzzzz

Haydi Kodumuzu Yazalım

Öncelikle dotnet core 2.2 projesi oluşturuyoruz.

Command Line’ımızı açıp, C:\ altında (tamamen tercih meselesi) bir klasör açalım.

C:\> mkdir JWTDemo

Ardından;

C:\JWTDemo> JWTDemo dotnet new webapi -o JWTDemolitos

Sonrasında Visual Studio Code’umuzu açalım.

C:\JWTDemo>code .

Startup.cs’imizin içerisini aşağıdaki gibi düzenleyelim.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace JWTDemolitos
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            //doğrulama şemalarımı ekliyorum.
            services.AddAuthentication(options => {
                        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    }
                )
                //options parametresinin özelliklerini belirliyorum.
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateAudience = true,
                        ValidAudience = "kuthaygumus.silverlab.com",
                        ValidateIssuer = true,
                        ValidIssuer = "kuthay-gumus.silverlab.com",
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(
                            Encoding.UTF8.GetBytes("bu_benim_muhtesem_uzunluktaki_muhtesem_saklanmis_guvelik_keyim"))
                    };
                    // token doğrulandığında ve çalışır olma süresi dolduğunda devreye giren iki eventim.
                    options.Events = new JwtBearerEvents
                    {
                        OnTokenValidated = ctx => {
                            //Gerekirse burada da gelen token içerisindeki çeşitli bilgilere göre doğrulama yapabilmemiz mümkün.
                            return Task.CompletedTask;
                        },
                        OnAuthenticationFailed = ctx => {
                            Console.WriteLine("Exception:{0}", ctx.Exception.Message);
                            return Task.CompletedTask;
                        }
                    };
                });
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAuthentication();
            app.UseMvc();
        }
    }
}

Controller Sınıfları

Şimdi iki adet Controller Sınıfı ekleyelim. Bir tanesinin ana görevi bize giriş biletimizi üretip vermek yani Token üretmek, diğeri ise demo bir metod olacak, yani kendisine geçerli bir token ile gelinip gelinmemesine göre cevap döndürecek.

TokenController’ımızı aşağıdaki gibi yazalım.

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace JWTDemolitos.Controllers
{
    [Route("token")]
    [AllowAnonymous()]
    public class TokenController : Controller
    {
        [HttpPost("getnewaccesstoken")]
        public IActionResult GetToken([FromBody]UserInfo user)
        {
            Console.WriteLine("User name:{0}", user.Username);
            Console.WriteLine("Password:{0}", user.Password);
            if (IsValidUserAndPassword(user.Username, user.Password))
                return new ObjectResult(GenerateToken(user.Username));
            return Unauthorized();
        }
        private string GenerateToken(string userName)
        {
            var someClaims = new Claim[]{
                new Claim(JwtRegisteredClaimNames.UniqueName,userName),
                new Claim(JwtRegisteredClaimNames.Email,"kuthaygumus@silverlab.com"),
                new Claim(JwtRegisteredClaimNames.NameId,Guid.NewGuid().ToString())
            };
            SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("bu_benim_muhtesem_uzunluktaki_muhtesem_saklanmis_guvelik_keyim"));
            var token = new JwtSecurityToken(
                issuer: "kuthay-gumus.silverlab.com",
                audience: "kuthaygumus.silverlab.com",
                claims: someClaims,
                expires: DateTime.Now.AddMinutes(1),
                signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
            );
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
        private bool IsValidUserAndPassword(string userName, string password)
        {
            //Demo için sürekli True döndürdük.
            //Internal bir NoSQL çözüm üzerinde, username ve passwordleri tutabiliriz.
            //Client, validation kontrolünden sonra, token almaya hak kazanmalı.
            return true;
        }
    }
    public class UserInfo
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

(Yukarıda gördüğünüz gibi UserInfo yardımcı classımız bulunmakta. IActionResult şeklinde dönen GetToken metodumuz [FromBody] özelliği kullanılarak istemcinin yolladığı Username Password bilgisini almakta.)

Son olarak da PromotionController’ımızı oluşturuyoruz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JWTDemolitos.Controllers
{
    [Authorize]
    [Route("promotion")]
    public class PromotionController : Controller
    {
        [HttpGet("new")]
        public IActionResult Get()
        {
            foreach (var claim in HttpContext.User.Claims)
            {
                Console.WriteLine("Claim Type: {0}:\nClaim Value:{1}\n", claim.Type, claim.Value);
            }
            var promotionCode = Guid.NewGuid();
            return new ObjectResult($"İşte Muhteşem Promosyon Kodunuz {promotionCode}");
        }
    }
}

Senaryomuza göre, geçerli username ve password ile login olan kullanıcımız bir promosyon kodu(token) alacak ve indirimden faydalanmak üzere diğer metodumuzu tetikleyecek 🙂

Gelelim Testlere

Testler için request atmamızı sağlayan programlar kullanılabilir. Ben bu aşamada Postman kullanacağım.

Web Api’mizi build edip çalıştırdığımızda dinlediği port üzerine requestler atıyoruz. İlk önce;

http://localhost:53635/token/getnewaccesstoken
{
   "username":"Kuthay",
   "password":"Promasyonİstiyorum"
}
POST
kuthaygumus.com-generatetoken

Şeklinde requestimizi yolluyoruz ve alt kısımda token’ımızı alıyoruz.

Bu token yani Promosyon Kodu almamıza yarayacak olan Fişimizi 🙂 PromotionController’daki methodumuza yolluyoruz. Düzenlememiz aşağıdaki şekilde olacaktır.

http://localhost:53635/promotion/new
Authorization - Type: Bearer Token
GET

Token value kısmınada, “getnewaccesstoken” metodumuzdan aldığımız Token’ı yazıyoruz.

kuthaygumus.com-tokenaccepted

Requestimizi yolladığımızda, metodumuz bize “İşte Muhteşem Promosyon Kodunuz” şeklinde promosyon kodumuzu döndürecek ve bizde akaryakıt indirimlerinde bunu kullanacağız 😀

Status 200 OK döndürdü gördüğümüz üzere.
Zaman aşımına uğramış veya hatalı bir token girdiğimizde ise 401 – Unauthorized yani “yetkisiz” hatası dönecektir.

Sonuç olarak, JWT bir erişim standartıdır ve OAuth 2.0 alt yapısında kullanılır. Bir sonraki yazımızda “Identity Server” ı inceleyip, JWT’yi daha efektir kullanmayı konuşuyor olacağız.

Mutlu günler dilerim.

Tarih:JSON Web Token

4 Yorum

  1. selcuk selcuk

    Merhaba,
    Örneği eksiksiz olarak uyguladıgimi düşünüyorum. Token alıyorum fakat PromotionController’a debug point koydum. Kod bu scope’a girmiyor. Postman’de kontrol ettiğimde 401 alıyorum. Bildigim kadarı ile 401 in anlamı expire ya da geçersiz token. fakat token’ı secret key ile jwt.io dan kontrol ettiğimde herhangi bir hata gözükmüyor. Bu kodun çalıstigindan emin misiniz? Yani token üretiyor olabilir fakat dedigim hatayı aliyorum

    • kuthaygumus kuthaygumus

      Merhabalar, örnek çalışmasaydı buraya ekran görüntüsü koyamazdım 🙂
      Startup.cs’inizi daha dikkatli kontrol edebilir misiniz ? O kısım genelde atlanıyor.
      İyi günler

  2. Hakan Hakan

    Çok güzel bir anlatım olmuş. Teşekkürler.

    • kuthaygumus kuthaygumus

      Rica ederim, umarım faydalı olmuştur 🙂

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir