Login Olunmayan Mobile Bir Projede, Token İle Güvenlik

Selamlar,

Bu makalede, authentication yapısı olmayan bir mobile projede, servisler üzerinden dataya erişimin, nasıl gelişi güzel yapılmaması gerektiğini tartışacağız. Aslında bu konu, tüm platformlar için geçerlidir. Örneğin herhangi bir haber portalına giderseniz, çoğunlukla çekilen haber servislerinin korumasız olduğunu görebilirsiniz. Aşağıda, Chrome üzerinde F12 ile Network dinlenerek, gidilen bir haber detay sayfanın servisi görülmektedir. Siz de postman üzerinden aynı servise request atarsanız, ilgili content’e kolaylıkla erişebilirsiniz.

Örnek Postman: Aşağıda görüldüğü gibi yakalanan servis her yerden, kolaylıkla çağrılabilmektedir.

Bu güvenlik sorununun bir nebze olsun giderilebilmesi için, farklı platformlarda çalışan, bir doğrulama metodolojisinin oluşturulması gerekmektedir. Öncelikle login sistemi olmadığı için, ilgili Token’ın bu senaryoda Mobile tarafından üretilmesi ve, yine bu Token’ın geçerliliğinin aynı yöntem ile web servisi tarafında yani backend’den, sağlanması gerekmektedir.

Şimdi gelin örnek amaçlı, .Net Core tarafında ilgili Token’ı oluşturan ve kontrol eden methodları yazalım.

.Net Core bir projede, ilgili methodlar projenin birçok yerinde kullanılabileceği için, Core katmanında Security altında aşağıdaki gibi Encryption class’ında tanımlanmıştır.

JWT TOKEN İŞLEMLERİ:

Core/Security/Encryption.cs / GetJwtToken(): Aşağıda görüldüğü gibi, Jwt kütüphanesi kullanılarak static bir SecurityKey ile, token oluşturulmuştur. Eğer aynı static key, diğer token üretilecek platformlarda da kullanılır ise, backend tarafında Jwt üzerinden validate işlemi global olarak kolaylıkla yapılabilmektedir.

  • var tokenHandler = new JwtSecurityTokenHandler()” : tokenHandler, kendisine verilen parametrelere göre token üreten Jwt nesnesidir.
  • var key = Encoding.ASCII.GetBytes(_Config.Value.PrivateKey)” : Tüm platformlarda, hem token üretimi hem de var olan token’ın test edilmesi amacı ile kullanılan ortak static keydir. Bu projede, Config dosyasından çekilmektedir.
  • var tokenDescriptor = new SecurityTokenDescriptor” : Yaratılacak token’ın, parametrelerinin tanımlandığı kısımdır.
    • new Claim(ClaimTypes.Name, deviceID)” : İsim olarak, mobileden gönderilen unique deviceID değeri kullanılmaktadır.
    • Expires = DateTime.UtcNow.AddHours(1)” : Oluşturulacak token 30 dakka süre ile geçerlidir. Yani biri bu token’ı ele geçirse, en fazla 30 dakka boyunca ilgili servislere erişebilecektir. Bu süre daha da kısaltılabilir.
    • SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)” : İlgili token, Sha256 algoritması ve belirlenen static secret key ile oluşturulur.
  • var token = tokenHandler.CreateToken(tokenDescriptor)” : Tanımlanan parametrelere göre, Token oluşturulur.
  • return tokenHandler.WriteToken(token)” : Oluşturulan token, string olarak geri dönülür.

Core/Configuration/Config.cs: Config’den çekilen, örnek amaçlı kullanılan SecurityKey’dir.

Core/Security/Encryption.cs / ValidateJwtToken(): Backend tarafında kullanılan, token doğrulama amaçlı bir methodur. Amaç, login olunmadan mobile veya diğer platformlardan yaratılan tokenların, request anında middleware’de araya girilip kontrol edilmesi ve doğru olması durumunda action’a gidilmesidir. İlgili token’ın geçerli olmama durumunda, “401 Unauthorized dönülmektedir.

  • var tokenHandler = new JwtSecurityTokenHandler()” : Jwt kütüphanesinin vazgeçilmez sınıfı, “JwtSecurityTokenHandler” Token doğrulama ve token üretmek için kullanılan sınıftır.
  • var key = Encoding.ASCII.GetBytes(_vbtConfig.Value.PrivateKey)” : Doğrulama için kullanılan Secret Key, üretilme anında kullanılan key ile aynı olmak zorundadır. Validate için, “byte[ ]” dizisine çevrilir.
  • tokenHandler.ValidateToken(mobileToken, new TokenValidationParameters” : Sorgulanacak token, secret key ile doğrulanır.

CONTROLLER:

Şimdi sıra geldi, Mobile için servis verilecek methodların işaretlenmesine.

Infrastructure/MobileTokenAttribute: Aşağıda görüldüğü gibi, mobileden çağrılacak methodlar “MobileTokenAttribute“‘ü ile işaretlenmişlerdir. Böylece işaretli Actionlar, yukarıda tanımlanan Token kontrolüne sokulur.

HomeCotroller/GetMobileCustomer(): GetMobileCustomer Action’ı, aşağıda görüldüğü gibi “MobileTokenAttribute” attribute’ü ile işaretlendiği için, JWT ile Token kontrolüne dahil olur.

  • [ServiceFilter(typeof(LoginFilter))]” : Makalenin devamında anlatılacak olan, “GetMobileCustomer()” Action’ına girmeden önce, MiddleWare’de çağrılan sınıftır.
  • public ServiceResponse<CustomerListModel> GetMobileCustomer()” : Mobile için, müşteri listesinin çekildiği methoddur.
  • var response = new ServiceResponse<CustomerListModel>(HttpContext)” : Katmanlı mimaride dönüş tipi, makalenin devamında anlatılacak olan ServiceResponse’dur. Ayrıca “CustomerListModel“, makalenin devamında anlatılacaktır.
  • response.List = _customerService.SearchCustomer(“”, 0, 10).List.ToList()” : Yine makalenin devamında anlatılacak olan, customerService’den ilk 10 müşteri kaydı alınır. Geri dönüş tipi bir liste olduğu için, ServiceResponse modelinin, “List” property’sine atanır.
  • response.Count = response.List.Count“: Paging için geri dönülen toplam kayıt sayısı, Count property’sine atanmıştır.
  • public IActionResult GetMobileToken(string deviceID)” : Örnek amaçlı, gerçek hayatta yayımlanmaması gereken bir methoddur. Bu örnekde, mobile yerine test amaçlı swagger’dan Jwt Token yaratılması için oluşturulmuş bir methoddur.
  • “[Infrastructure.IgnoreAttribute]” : GetMobileCustomer Action’ının işaretlenmesi, LoginFilter Action Filter’a takılmaması, yani ignore edilmesi için tanımlanmıştır.
  • var jwtToken = _loginService.GetMobileToken(_deviceID).Entity” : Yukarıda Encryption class’ında tanımlanan “GetJwtToken()” methodunda, loginService’i çağrılarak oluşturulan token, geri dönülür.
  • public IActionResult ValidateJwtToken(string mobileToken)” : Yine dışarı açılmayacak, sadece swagger’da test amacı ile kullanılacak, JwtToken’ın geçerli olup olmadığını belirleyen bir servisdir.
  • var jwtToken = _loginService.ValidateJwtToken(mobileToken).Entity” : Yukarıda Encryption class’ında tanımlanan “ValidateJwtToken()” methodunda ,yine  loginService’i çağrılarak ilgili Token’ın geçerliliği doğrulanır.

MODELLER:

Core / ServiceResponse : Tüm servislerden dönen global model, ServiceResponse’dur. Makale amacı ile alanları, gerçek hayata göre kısıtlı tutulmuştur.

  • _customerService = customerService” : Customer Service, “Dependency Injection” ile sisteme dahil edilmiştir.
  • public bool HasExceptionError { get; set; }” : Serviste bir hata oluştuğunda, ilgili alan atanır.
  • public class ServiceResponse<T> : IServiceResponse<T>” : Generic Tipde, kendisine gönderilen tüm modelleri kapsamaktadır.
  • public IList<T> List { get; set; }” : Geriye dönüş tipi bir liste ise, bu property atanır.
  • public T Entity { get; set; }” : Geriye dönüş tipi tek bir model ise, bu property atanır.
  • public int Count { get; set; }” : Paging için toplam kayıt sayısı, burada tanımlanır.

Core/Models/Customer/CustomerListModel: İstenen müşteri raporu için gerekli olan alanlar, aşağıdaki gibi bir ViewModel’de tanımlanmıştır. Bu, aslında bir çeşit adaptor’dır. Yani database’den dönen tüm alanlara ihtiyaç duymayan sistemin, sadece kendi ihtiyacı olanları aldığı bir yapıdır. Not: AutoMapper kütüphanesi, ilgili alanların otomatik eşleşmesi için kullanılabilecek güzel bir kütüphanedir.

SERVİSLER:

Services/Customers/ICustomerService: GetMobileCustomer() Action’ından çağrılacak servisin interface’i aşağıdaki gibidir. Makale amaçlı “SearchCustomer()” adında, tek bir methodu bulunmaktadır.

Services/Customers/CustomerService: Aranan isme göre , paging yapılarak bulunan müşterilerin kaydının döndürüldüğü “SearchCustomer()” methodu, aşağıdaki gibi tanımlanmıştır.

  • private readonly IRepository<DB.Entities.VwCustomerProducts> _customerProductRepository” : Bu projede, Entity Framework kullanılmıştır. DB tarafında “VwCustomerProducts” adında bir view yaratılmış ve linq query, bu view üzerinden yapılmıştır. Makalenin devamında anlatılacak “Repository” katmanı ile ilgili view sisteme dahil edilmiştir.
  • “_customerProductRepository = customerProductRepository”: İlgili View, Dependency Injection ile sayfaya dahil edilmiştir.
  • “ServiceResponse<CustomerListModel> SearchCustomer(string name, int pageNo, int pageSize)” Bu servis de geriye, “ServiceResponse<CustomerListModel>” dönmektedir. Parametre olarak, aranacak müşteri ismi, başlangıç sayfası ve çekilecek adet beklemektedir.
  • _customerProductRepository.Table” : Repository katmanındaki Table == DB’deki View’a karşılık gelmektedir. Linq Query, bu view üzerinden çekilmektedir.
  • Where(k => EF.Functions.Like(k.CustomerId ?? string.Empty, $”%{name}%”))” : Performans açışından EF.Functions.Like ve Contains performans anlamında artık, aynıdır. Kulağınıza küpe olsun :) Aranacak isme göre ilgili müşteri kaydı, “Like” ile bakılır.
  • .OrderBy(c => c.CustomerId) .Skip(pageNo * pageSize) .Take(pageSize)” : Müşteri numarasına göre sıralama(OrderBy) ve paging işlemleri Skip() ile atlanacak kayıt sayısı, Take() ile de alınacak toplam kayıt sayısı tanımlanır.
  • var models = query.Select(s => new CustomerListModel()” : Çeklen data, “List<CustomerListModel>“‘e doldurulur. Bu işlem AutoMapper kullanılarak daha pratik bir şekilde yapılabilirdi.
  • response.List = models” : Geri dönülecek data tipi list olduğu için,==> ServiceResponse modelin “List” porperty’sine ilgili model atanmıştır.

Login/ILoginService: Test amaçlı, token yaratma ve onaylama işleminin yapıldığı servisin interface’i dir.

Login/LoginService: Aşağıda, HomeController’dan çağrılan LoginService üzerindeki GetMobileToken() ile, aslında mobile tarafında yaratılması gereken Token’ın, swagger’dan test amaçlı oluşturulması için yazılmış methodu görülmektedir. Ayrıca yine HomeController’dan çağrılan ValidateJwtToken(), middleware’de mobile tarafından gelen tüm requestlerde gönderilen token’ın geçerliliğini kontrol etmektedir. İlgili token’ın geçerli olması için, backend ve mobile tarafında secretKey’in aynı olması gerekmektedir. Geçerli bir token gelmemesi durumunda, geriye 401 Unauthorized dönülecektir.

REPOSİTORY:

IRepository: Entity Framework üzerindeki Context’e, Table attribute’ü ile ulaşılmaktadır. Makale için kısaltılmıştır.

Repository: DBContext üzerinde Insert, Update ve Delete gibi genel işlemlerin yapıldığı bir sınıftır. Bu örnek de sadece DBContext’e erişmek amacı ile, Table property’si tanımlanmıştır. Kısaca Repository katmanı bir projede, Servis ile DB katmanı arasında genel Crud işlemlerinin yapıldığı, tüm temel DB işlemlerini kapsamaktadır. Bu genel işlemler, Servis katmanında ayrıca yazılmamalıdır.

ActionFilter:

Geldik LoginFilter ile .Net Core üzerinde yazılan Custom ActionFilter’a:

Infrastructure/ LoginFilter: Controller veya Action üzerine konan “[ServiceFilter(typeof(LoginFilter))]” işaretlemesi ile ilgili filter’a girilir. Controller üzerine konur ise, altındaki tüm Actionlar için ilgili Filter çalıştırılır. Eğer sadece bir Action üzerine konur ise, sadece ilgili Action için LoginFilter devreye girer.

  • _encryption = encryption” : Dependency Injection ile “GetJwtToken()” ve “ValidateJwtToken()” methodları, ilgili servis üzerinden çağrılır.
  • public void OnActionExecuting(ActionExecutingContext context)” : Bir Action’a girmeden önce çalıştırılan methoddur. Kısaca, kapıdan duran bir bekçidir :)
  • if (HasMobileTokenAttribute(context)) : Üzerinde “HasMobileTokenAttribute” işaretlemesi olan Actionlarda, MobileToken kontrolü yapılır.
  • string authHeader = context.HttpContext.Request.Headers[“Authorization”]” : Mobilde oluşturulan JWT Token, Header’dan ==> “Authorization” keyi ile okunur.
  • if (authHeader != null && authHeader.StartsWith(“Bearer”))” : Eğer token yok ise veya “Bearer” kelimesi içinde geçmiyor ise, geriye ==> “401 Unauthorized” sonucu dönülür.
  • var mobileToken = authHeader.Substring(“Bearer “.Length).TrimStart()” : Header’dan gelen JWT Token içinden, “Bearer” kelimesi çıkarılır.
  • “if (_encryption.ValidateJwtToken(mobileToken))” : Gönderilen JWT Token’ın geçerli olup olmadığna bakılır.
  • context.Result = new UnauthorizedResult()” : Geçerli değil ise  ==> “401 Unauthorized” sonucu dönülür.
  • public bool HasMobileTokenAttribute(FilterContext context)” : Mobile üzerindeki tüm işaretlemeler yani Attributeler gezilerek ==> “MobileTokenAttribute” ile işaretli mi diye bakılır.

Bu makalede Authorization işlemleri olmayan yapılarda, kısıtlı da olsa güvenliği nasıl arttırabileceğimizi hep beraber tartıştık. Jwt kullanan projelerde istendiği zaman logout olunamamasından dolayı, benim pek de tercih ettiğim bir kütüphane değildir. Gerçi şimdi Jwt kullanan arkadaşlar, ilgili token’ın Local Storage’dan silinmesi durumunda işin çözüldüğünü düşüneceklerdir ama, sözüm ona silinen bu token’ı önceden ele geçiren bir kişi, Jwt tarafında Expire süresi dolana kadar kullanabilmektedir. Çünkü Jwt, bir token’ın expire süresi dolmadan onu emekli edemez! Ama JWT kütüphanesinin özellikle token üretiminde, farklı platformların birbirleri ile anlaşması durumunda, gayet güzel çalıştığını rahatlıkla söyliyebilirim.

Şimdi makalenin başında da verdiğim örnek de olduğu gibi, aklınıza peki bunu web ortamında nasıl yapabilirdik diye bir soru gelebilir. Yani bir haber portalından, istenen bir habere tıklandığında, javascript tarafında ilgili Jwt Token nasıl üretebilir ? Onu da isterseniz başka bir makalede tartışalım ?

Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın. Sağlıcakla kalın.

Source :

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

4 Cevaplar

  1. Kenan dedi ki:

    Peki mobile uygulamamızı decompile ederek içerisinden bu secret keyi alamazlar mı? Bu kısmı çok anlayamadım.

  2. Mahmut dedi ki:

    Hocam Merhaba, Makaledeki uygulamanın kodlarının tamamını Github’a yükleyebilir misiniz?

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.