JSON Web Tokens(JWTs) ile Spring Boot uygulamalarında Authentication & Authorization 🔑

Ugur Cem Ozturk
6 min readDec 6, 2017

--

JWTs, Client-Server arasında tokenize işleminin JSON objesi kullanılarak yapılmasını sağlar. Server tarafında belirlenmiş bir Secret Key ve genellikle HMAC Algoritması kullanarak Token imzalanır.

JWTs Workflow

JWT bir Encryption değil, Encoding işlemidir!😱

Detaylar ve demo için:

Öncelikle üzerinde çalışacağımız backbone’u indirelim.

git clone -b backbone https://github.com/ugurcemozturk/JWT.git

Basit bir MVC yapısındaki bi modülleri kısaca açıklamak gerekirse:

  • Project: Entitiy modelimiz, data olarak kullanacağımız/kaydedeceğimiz yapı
  • ProjectRepository: Modelimizin database işlemlerinden sorumlu yapı. Şimdilik in-memory DB olan HyperSQL işimizi görecektir.
  • ProjectController: Server’a entity’miz ile ilgili gelen requestleri handle eden ve bir response dönen yapı.

İlk olarak bir Model sınıfı yazmalıyız. “domain package” içerisinde Developer sınıfını yaratalım. Bu sınıf, giriş yapacak kullanıcıları temsil eden bir Entity sınıfıdır.

Şimdi de bu sınıfın veri kayit işlerinden sorumlu repository’mizi yazalım.

Bu repo’muz JpaRepositöry’i miras alıyor. Bu sayede save(),delete() gibi methodlar pre-defined geliyor. Bunlar ihtiyaçlarımızı karşılamaya yeterli ama ek olarak findByUsername() methodü ile kullanıcı adını argüman olarak verip ilgili kaydı döndürebiliyoruz, bunun için herhangi bir SQL yazmamıza gerek yok. Jpa Rocks ;)

Şimdi’de yeni bir developer register olmak istediğinde gideceği endpoint’i ve controller’ini yazalım.

Burada basitçe /sign-up path’ine HTTP POST request’i yapıldığında, gelen request body parse edilip şifre alınır ve encrypt edilir. Kullanıcının bilgileri DB’ye kaydedilir.
Encryption işlemini yapan bCryptPasswordEncoder’in instance’ını oluşturmamız gerekli. Bunu Application’ı run ettiğimiz base class’ta yapmalıyız. Bende bu sınıfın adı “DemoApplication”. Aşağıdaki kod parçasını ekleyelim.

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

Şu an kullanıcılar register olabiliyor. Şimdi Authorization ve Authentication işlerini halledelim.

Authorization ve Authentication Filtreleme

Şimdi yapacaklarımız:

  • Login olan kullanıcılara JWT assign etmek için Authentication Filter
  • Authenticated kullanıcıların yeni requstlerindeki JWT’leri validate etmek için Authorization Filter
  • DAO olarak user-spesific verileri yüklemek için Spring Security içinde gelen UserDetailsService interface’inin implementasyonu

Authentication Filtreleme

Bu sınıfımızın iki görevi var. Başarılı login’lere JWT atama ve yeni authorized requestlerin JWT’sini kontrol etme. İmplementasyonu şu şekilde;

  • EXPIRATIONITIME = Token’in geçerlilik süresi
  • SECRET= Token’in HMAC algoritması ile imzalanırken kulladığı secret key, istediğimiz değeri verebiliriz.
  • TokenPrefix =Authorization Header’ındaki auth tipi.(basic, bearer, ..)

İki fonksiyonumuz var. İlki olan attemptAuthentication fonsiyonunda gelen request’teki Header’i parse edip Token’i alıyoruz. Ardından UsernamePasswordAuthenticationToken sınıfının bir nesnesini oluşturup JWT’mizi set ediyoruz. Nesnesini oluşturduğumuz bu sınıf kullanıcı adı ve parolayı saklar. getCredentials() ve getPrinciples() methodları ile bunlara erişebiliriz. (Further info: https://goo.gl/bu6Qp2)

Ardından, succesfulAuthentication ile başarılı bir şekilde login olmuş kullanıcıya JWT yollayacağız. Bunun için Jwts sınıfının builder() methodu ile Payload kısmı için subject ve expiration time’i set edip, kendi tanımladığımız SECRET ile imzalayıp Tokenimizi oluşturuyoruz ve Header’a ekliyoruz.

Şimdi başka sınıflarda da kullanılacak, Header’i oluşturmak için gerekli constanlar ve sign-up path’ini içeren bir constants sınıfı oluşturalım. Hiçbir fonksiyon yok, sadece kod kalabalığını önlemek için.

Authorization Filtresi

Constructor’da kalıtım altığımız soyut BasicAuthenticationFilter sınıfı argüman olarak authenticationManager ister, bu authentication requestlerinin submit edildiği Bean’dır. Yine iki methodumuz var. İlk methodumuzda kalıtım aldığımız soyut BasicAuthenticationFilter sınıfının doFilter() methodunu implemente ediyoruz. Bu method, korunan bir kaynağa request geldiğinde bu request’i yakalar, JWT’yi validate eder ve herşey uygunsa kullanıcı authentication bilgilerini SecurityContextHolder ile SecurityContext’in içine yazar ve kaynağı client’a sunar.
İkincisi JWTAuthenticationFilter sınıfındaki successfulAuthentication() methoduyla benzer, Authorization Header’ından JWT değerini okur ve token’in geçerliliğini onaylar.

Authentication/Authorization Filtrelerini Implemente Etme

Su ana kadar gelen isteklere JWT yaratan, gelen JWT’lere gore filtreleme yapan iki Filter sinifi yazdik. Simdi bunlari Spring Security’e implemente edelim. Bunun icin bir WebSecurityConfig sinifi yazalim.
Bu sinifla hangi kaynaklara izin verip vermedigimizi ve hangi filtreleri kullanacagimizi yazacagiz. Genel guvenlik unsurlarinin hepsi burada yer alir.

3 methodumuz var. Bunlarin gorevlerine bakalim;

  • configure(HTTPSecurity http)= Kisaca burada hangi methodlara ve path’lere izin verdigimizi belirtiyoruz. Oncelikle CORS’a izin verip CSRF’i disable ediyoruz. Sonrasinda /sign-up path’ine POST request’e izin verip, geri kalan tum kaynaklar icin authentication bekledigimizi soyluyoruz.
    Ardindan az once yarattigimiz iki Filter’i ekleyip, session olusturmayi kaldiriyoruz. Buni JWT ile saglayacagiz.
  • configure(AuthenticationManagerBuilder auth)= auth.userDetailsService(T) methodu, Spring Docs’ta arguman olarak verilen userDetailsService’e yani user-specific bilgileri authenticate ettigini soyluyor. Dogrusu burasi benim de biraz kafami karistirdi. Daha aciklayici bir yorumunuz varsa lutfen yorum olarak belirtin!🤓 passwordEncoder kisminda ise encryption icin kullanacagimiz sinifi set ediyoruz.
    Ardindan, bir in-memory user ekliyoruz. Kullanici adini ve parolasini istediginiz gibi verebilirsiniz.
  • corsConfigurationSource()= Burada Cross Origin Resource Sharing(CORS) izinlerini set ediyoruz, simdilik herhangi kaynaktan gelen requestlere izin verdik.

Son adim!

Spring Security, UserDetailsService DAO’sunu bize interface olarak verir. Yukarida kullanimi yaptigimiz bu interface’in soyut methodlarini implemente edecegiz. Yeni bir package yaratalim, adi “service” olsun. Bu sinifi service package icinde yazalim.

Bu layer, mantik olarak, Read/Write islemleri yapan DeveloperRepository sinifimla aramda bir katman, bir Data Access Object(DAO). Yani dataya ‘ulasmak’ icin bir layer. Gordugunuz gibi loadUserByUsername() methodu ile verilen username’i Repository’mden bulup varsa bana getiriyor, ona access etmemi sagliyor. Neden mi boyle? Separation of Concerns!

Test Edelim! 🍻🍻

Hersey tamam. Simdi sistemimizi test edelim ve gelen token degerini bulmaya calisalim. Test icin cURL ya da POSTMAN kullanabilirsiniz. Daha iyi gorsel deneyim icin ben POSTMAN kullanacagim.

Buraya kadar okuyarak geldiyseniz test etmeniz icin size iki guzel secenek sunmak istiyorum.

  1. Local’de deploy etmeden, sadece test edecekseniz; Heroku Cloud’a ben deploy ettim, buraya HTTP Request’leri yollayarak test edebiliriz! (Heroku gercekten mukemmel! 🙏). Ben yazinin devaminda, buradan test edecegim.
    Test URL: https://jwt-springboot.herokuapp.com/
  2. Local’de calistirmak icin github’dan clone’layabilirsiniz.
git clone https://github.com/ugurcemozturk/JWT.git

Senaryo #1 — Access Denied

https://jwt-springboot.herokuapp.com/ adresine GET Request yollayalim. Access Denied ile geri donmesi gerekli.

.

.

.

Done!

Senaryo #2 — Yeni Kullanici Ekleme

Burada /developers/sign-up path’ine Body bolumunde JSON olarak set ettigimiz degerler ile POST request yapiyoruz. Sonuc basarili!

Ornek olmasi acisindan, hatirlarsaniz CORS’u enable etmistik. Asagidaki access-control-allow-credentials:true Header’i bunu dogruluyor.

Senaryo #3— Sisteme Giris Yapmak

Az once register ettigmiz kullanici bilgileri ile /login sayfasina POST Request atalim. WebSecurityConfig sinifinda, bu kaynaga atilacak POST isteklerine izin vermistik.

Veee Boom💣..

Headers’a bakin. Authorization Header’i altinda “Bearer eyJhb..” diye baslayan bir Header’imiz var. Iste server’in bize atadigi JWT!

3 parcadan olusuyor ve bu parcalar “.” ile ayriliyor. Formati su sekilde: HEADER.PAYLOAD.SIGNATURE

Senaryo #3 — Yeni bir Project POST Etmek

Once Body kismina gelip, modal yapimiz olan Project sinifinin “description” degiskenini set edelim.

JWT’mizi kullanarak POST Request yapmak icin, Authorization Header’ini kopyalayip, POSTMAN’daki Hearders kismina yapistiralim. Ardindan request’i yollayalim.

Ta daa.. POST islemi basariyla gerceklesti ve GET ile de bunun dogrulugunu sagladik.

Tum bu requestlerimizi yollarken Authorization’a JWT degerini ekledik ve tum filtreleri asarak isteklerimizi dogru sekilde gerceklestirdik.

Uzuuun yazimin sonuna geldik. Bu yaziyi ogrenirken eszamanli olarak yazdim. Tanimlarda, aciklamalarda bir hata ya da eksiklik oldugunu dusunurseniz, ya da gelistirmeyi dusundugunuz bir bolum varsa lutfen yorum olarak bana iletin.

Sabriniz icin tesekkurler.

Ugur Cem.
ugurcem.com

🍻✌️

Better to have a look at:

Kaynak: https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/

--

--