obenplus.com | oben+ | Hikayesi bol bir yazılımcının kaleminden, teknoloji dünyasını en önemli ve en ince ayrıntısına kadar takip etmek, bir yazılımcının hayatını, yaşantısını gözlemlemek isteneyenler için...

Spring Boot ve Spring Security İle Stateless Token Bazlı Rest Api

Günümüzde orta ölçekli firmalarda ve bir çok kurumsal web uygulamasında kullanılıyor Spring. Her firmanın kendine göre bir mimarisi oluyor. Çoğunluk halen SOAP üzerinden iletişimde kalmayı tercih etse de -haklı sebepleri de var- artık trend REST üzerine kaymakta. (Rest ile ilgili yazılarımdan birine buradan, diğerine buradan ulaşabilirsiniz.)

Spring Boot ise popülerleşme yolunda büyük yol katetti bile. Peki nedir bu Spring Boot ? Spring Boot tek amaçlı frameworklerle ya da basmakalıp kodları ve ayar işlerini neredeyse ortadan kaldıran devingen dillerle ilişkilendirebileceğiniz bir kod yazma yaklaşımıdır. Bakınız bunu ben demiyorum, bunu Pivotal’ın danışman ürün pazarlama müdürü Pieter Humphrey diyor 🙂

Ben de bugün kolları sıvadım, güzel bir blog yazısı yazmaya karar verdim bu konuyla ilgili.  Şöyle bir senaryomuz var : Bir api katmanımız olacak ve bu katmana kullanıcılarımız login olarak api üzerindeki metodları token bazlı koşabilecekler. Burada karıştırmamamız gereken bir nokta, bu yazımda, Oauth ya da Oauth2 üzerine bir access_token mantığından bahsetmiyoruz. Çünkü bizim senaryomuzda, 3rd party bir client üzerinden erişim olmayacak. Biz API mizi yazacağız ve kurumumuzun Web uygulaması da Mobil uygulaması da aynı api üzerinden çalışacak.

Konumuzun temelini authorization süreçleri oluşturduğundan, aşağıdaki lifecycle ı dikkatle incelemenizi rica ediyorum. Aslında yapmak istediğimiz şey tam olarak da bu.

authBizim örneğimizde, /api/v1/user pathine erişmek isteyeceğiz. Bu path, sadece ROLE_ADMIN yetkisine sahip kişilerin görebileceği bir path. Uygulamamızın token bazlı süreci işletirken buna uygun çalışması son derece kritik.

Akış ise şu şekilde olacak;

  • Kullanıcı /api/v1/user pathine erişmeye çalışır
  • AuthenticationServer kullanıcının 401.UNAUTHORIZED olduğunu döner. Çünkü bu pathe ulaşabilmek için bir token gerekmektedir.
  • Bu sefer token almak için /api/v1/login pathine headerda username ve password değişkenlerimiz gönderilir.
  • AuthenticationServer 200.OK döner ve headerda token değerini bildirir.
  • Artık bu token değeri belirli bir rol için yetkiye sahip ve tekrar /api/v1/user ı çağırabiliriz

 

Böyle bir yapı kurabilmek için öncelikle yapmamız gereken SpringBoot uygulamamızı Maven in de yardımıyla kurmak.

Bunun için pom.xml e spring boot için gerekli dependency lerimizi ekliyoruz. Son olarak aşağıdaki pom.xml i inceleyerek kendnize importlarınızı yapabilirsiniz.

Projenin örnek kodlarını zaten paylaşacağım için sizlere önemli kısımlarından bahsedebileceğim kısımların kodlarını anlatmak istiyorum.

WebSecurityConfiguration

Burada önemli olan üç mesele var.

  1. LinUserDetailService : Bu servisin amacı, Spring’in UserDetailService interface ini custom olarak implemente etmek. UserDetailService in amacı, user bazlı bilgileri dönmek. Bu generic class ile Spring’e username, password, account expire olmuş mu olmamış mı, account locked olmuş mu olmamış mı, account enabled mi disabled mi , yetkiler neler vb. bilgileri dönmek. Spring Security, UserDetailService in override edilen loadUserByLogin metodunun içerisindeki UserDetail class ını kullanarak, bütün bu Rest Api yetki kontrolünü yönetecek. Yani kullanıcı bazlı bilgileri alırken bu servisi kullanacağız.
  2. TokenAuthenticationService : Bu servisin de amacı, token üzerindeki authentication işlemlerini yapmak. Örneğin username ve password ü kontrol edip, token yaratıp bunu user objesinde (bu user bilgisi database de olabilir ) tutmak. Ya da users pathini çağırdığınız zaman token gönderdiğiniz için bu tokeni valide edip, timeout olmadığına bakarak işlem yapmak.
  3. configure metodu. Bu metodda bir http request inin hangi kontrollere uğrayacağını hangi filterlar üzerinden geçeceğini ifade ediyoruz Spring’e.
    1. eğer .html, .css, .js ya da /auth/** lu bir url gelirse bu url ler için authentication yapma.
    2. Bunların dışına kalan requestleri authenticate etmen gerekiyor.
    3. Bu authentication işlemlerini yaparkeneğer /auth/login pathine geliniyorsa StatelessLoginFilter kullan, diğerleri için StatelessAuthenticationFilter kullan.

Bu aşamaya kadar Spring Security konfigurasyonumuzu yapmış oluyoruz. Şimdi de biraz da bu filtrelerimizin özelliklerini inceleyelim.

TokenAuthenticationService

TokenAuthenticationService ‘in amacı header’deki username, password ya da X-AUTH-TOKEN değerlerini kontrol edip buna göre gerekli dönüşleri yapmaktır. Bunun için aşağıdaki sınıfı inceleyebilirsiniz;

Yukarıdaki sınıftan da görüleceği üzere getAuthentication ve getAuthenticationForLogin adında iki metodumuz var.

getAuthentication metodu, X-AUTH-TOKEN yani token var iken kullanılıyor. Yani StatelessAuthenticationFilter tarafından kullanılıyor. token ‘ı alıp user bilgisini bulmaya çalışıyor. (Örneğin token database de ise, bu tokenın karşılığı olan user bilgisi çekiliyor. Burada expire süre kontrolü de yapabilirsiniz.) User bulunursa, bu durumda 200 OK dönülüyor. Eğer bulunamazsa 401 Forbidden dönüyoruz. Aslında mantık oldukça basit.

getAuthenticationForLogin metodu da username ve password alıyor, bu bilgilere göre kullanıcıyı buluyor ve 200 OK dönülüyor. Şifre yanlış olsaydı 401 dönülecekti. (Kullanıcı bulma aşamasında basit bir şekilde test yapabilmek için, HasMap üzerine userları koydum. Yani bir db bağlantısı yok. Sizin örneğinde bir data repository üzerinden yürütmeniz lazım bu süreçleri)

addAuthentication metodu da, eğer kullanıcı onaylanırsa, bu durumda header da kullanıcıya bir token dönülmesini sağlıyor. login metodundan dönülecek bu X-AUTH-TOKEN parametresi bu userın sonraki API kullanımlarında gerekecek.

Ekran Resmi 2015-10-16 19.58.10

Yukarıdaki yapıdan da görüldüğü üzere aslında sistem o kadar da kompleks bir yapıda değil. Eğer 3rd party bir client ile işiniz yoksa oAuth veya oAuth2 ile uğraşmanıza gerek yok, başınızın da çok fazla ağrımasını önlemiş olursunuz.

StatelessLoginFilter

WebSecurityConfiguration’dan da hatırlayacaksınız, configure metodunda filterlar tanımlamıştık. Bu filterlardan bir tanesi /api/login aşamasında çağrılıyordu. Bu filterın türü StatelessLoginFilter olacaktı. Bu filterin içeriği oldukça basit;

Bu filterda yaptığımız tek şey TokenAuthenticationService’e gerekli işlemleri yaptırmak ve sonuçlarını response üzerinden dönmek. Aynı şekilde StatelessAuthenticationFilter de de durum aynı.

StatelessAuthenticationFilter

Filterlar ve AuthenticationService arasındaki bağlantı da bu durumda aşağıdaki gibi olmaktadır;

Ekran Resmi 2015-10-16 20.11.22Elimden geldiğince anlatmaya çalıştım 🙂 Anlatması o kadar kolay değil, fakat aşağıdaki cURL örneklerini incelerseniz, olayın akışı daha rahat canlanacaktır diye düşünüyorum. Sonrasında da kodları incelersiniz.

Komut 1-Login olmadan /users pathine erişmek

Çalıştırılan komut;

Aldığımız yanıt;

Komut 2 – ADMIN olmayan kullanıcı ile sisteme Login olmak ve /users pathine erişmek

Sistemde 2 kullanıcımız var. Bir tanesi OBEN diğeri EREN. OBEN kullanıcısının rolu ROLE_ADMIN ve EREN kullanıcısının rolu ROLE_USER.

WebSecurityConfiguration classından aşağıdaki kodu hatırlayalım;

Yani users pathine erişebilmek için ADMIN rolünde olmak gerekiyor. Burada dikkatli gözler ROLE_ADMIN demiştin şimdi neden ADMIN dedin diyeceklerdir 🙂 Evet spring security rol isimlerini ROLE_ prefix i ile alıyor.

Bu case için beklentimiz EREN ile login olabilirsek ve bir token alabilirsek bu token ile /users  pathine erişememek.

Bu komuta aldığımız yanıt içerisinde headerdeki X-AUTH-TOKEN değerine ve ilk satırdaki 200.OK e dikkat.

Yani burada aşağıdaki X-AUTH-TOKEN kullanılacak;

Şimdi de bu tokenı kullanarak /users pathine erişmeye çalışalım. Bunun için aşağıdaki komutu çalıştırıyoruz;

Aldığımız yanıt ise şu şekilde;

Gördüğünüz gibi 403 hatası aldık. Şimdi de aynı senaryoyu OBEN yani ROLE_ADMIN bir kullanıcıyla deneyelim.

Komut 3 – ADMIN olmayan kullanıcı ile sisteme Login olmak ve /users pathine erişmek

 

Önce yeri gelmişken hatalı şifre ile login olmaya çalışalım;

Yanıtımız bu şekilde (401 Aldık)

Şimdi de doğru şifre ile login olup tokenimizi alalım.

Buna göre token için aşağıdaki bilgiyi kullanacağız;

 

Bu token ile user bilgisini çekmeye çalışalım;

Gördüğünüz gibi bu sefer user bilgisine erişebildik.

Ekran Resmi 2015-10-16 20.42.29

Umarım faydalı olmuştur arkadaşlar. Kodları incelemek isteyen arkadaşlarım bana ulaşabilirler. Her yerde bilgim mevcut 🙂

Sevgiler

 

 

  • Ibrahim Erol

    Merhaba, Projenin kaynak kodlarını paylaşabilir misiniz github vb üzerinden veya zip olarak? Teşekkürler

  • Oben Işık

    Merhaba İbrahim, Bir projemde kullandığım için kaynak kod paylaşamamaktayım maalesef :/ Ama soracağın sorular olursa yardımcı olmaya çalışırım.