.Net Core’da Merkezi Login Sistemi İle Dağıtık Sistemlere Giriş Yapma

Selamlar,

Saga Prefecture, Japan

Bu makalede, farklı DBlere, Redis’e ve benzeri araçlara sahip sistemlere, tek bir yerden Login olma konusunu Backend tarafından inceleyeceğiz. Kısaca amaç, farklı uygulamara tek bir Login Sayfasından bağlanmak.

Öncelikle gelin yeni Ana Login sayfasının, DB ve Servislerini yazalım.

İlk önce DB’den başlıyalım. Aşağıda görüldüğü gibi Companies tablosunun başlıca fieldları, Şirket Adı, Şirket Kodu, CompanyUrl ve ApiUrl’dir. Login olacak kişi, CompanyCode’u elle girmekte, ilgili şirketin UserName ve Password’ünü girerek uzaktan login olmaktadır.

Gerçek Senaryo:

Gerçek senaryoda amaç, birden fazla alt şirketi olan büyük bir şirketin yöneticisinin, istediği şirkete tek bir Ana Login sayfasından girmesidir. İstenildiğinde, ilgili şirketlerin kendine özel Login sayfası da bulunmaktadır.

CopmanyCode, ApiUrl : Gönderilen CompanyCode’a göre, ApiUrl bulunur. Girilen UserName ve Password, bulunan ApiUrl’e Post edilerek Login işlemi yapılır.

CompanyUrl: Eğer Login işlemi başarılı ise, geriye Result olarak dönen UniqueID Key alanı, Fornt-End uygulama (Angular) üzerinde, CompanyUrl adresine => UrlParamtere olarak Redirect edilir.

Companies (Table):

MainLogin adında .Net Core bir WebApi projesi yaratılır.

“Başkalarını bilen kimse bilgili, kendini bilen kimse bilgedir. Başkalarını yönetmek kuvvettir; kendini yönetmek iradedir.” ―Lao Tzu

LoginModel: Öncelikle Request çekilecek, client’ın credentiallarını post edeceği LoginModel, aşağıdaki gibi tanımlanır. Bu model ile request çekilecek servis yolu, client tarafından el ile girilen CompanyCode ile bulunur.

LoginReturnModel: Girilen CompanyCode ile bulunan, ilgili firmanın servisine Login olmak amacı ile post edilen data model aşağıdaki LoginReturnModeldir.

MainLoginResultModel: İstenen firma için, Login işlemi gerçekleştikten sonra geriye dönülen data modelidir. Burada en önemli field, “Key” propertysidir.

Post: Ana sisteme, esas login olunan servisdir.

  • string encryptedPassword = _encryption.EncryptText(model.Password)“: ClientSide’dan gönderilen Password şifrelenir. Amaç güveliğin arttırılmasıdır. Eğer service Trace edilir ise, password’ün okunur bir şekilde görülmesi engellenir. Encryption sınıfının kodlarına, şu makaleden erişebilirsiniz. => https://www.borakasmer.com/net-core-uzerinde-config-redis-ve-elasticsearch-icin-guvenlik/
  • var company = _context.Companies.AsNoTracking().Where(a => a.CompanyCode == model.CompanyCode).FirstOrDefault()“: Girilen CompanyCode’a göre, erişilecek servis yolu, DB’den çekilir. Her şirket için farklı bir servis yolu bulunmaktadır.
  • LoginReturnModel returnModel = new() { UserName = model.UserName, Password = encryptedPassword }“: Bağlanılacak sisteme ait Credential bilgileri, LoginReturnModel’e doldurularak post edilir.
  • var resp = await client.PostAsync($”http://{company.ApiUrl}/api/Login/MainLogin”, requestContent)” : Makalenin devamında anlatılacak olan “MainLogin” servise post atılıp, remote login işlemi gerçekleştirilir.
  • var mainLoginResultModel = JsonSerializer.Deserialize<MainLoginResultModel>(content)”: İlgili servisden geriye, “MainLoginResultModel” tipinde bir sonuç dönülür. Bu modelde client için önemli olan iki alan vardır.Biri “Key“, diğeri de “CompanyUrl“‘dir.
  • mainLoginResultModel.Url = company.CompanyUrl“: Client-Side’da login işlemi başarı ile gerçekleşir ise, Redirect olunacak url bu propertyde tanımlanır. Bu sefer redirect olunacak Url, Remote’da Client-Side (Angular) tarafındaki, Login sayfasının yoludur.

Post():

Genel Özet:

Companies tablosuna aşağıdaki gibi bir sorgu çekilir ise CompanyCode, Ana Giriş sayfasında elle girilen koddur. Bu koda bağlı olarak, Server-Side’da “ApiUrl” ile gidilecek Remote şirketin WebApi servisinin yolu belirlenir. İlgili servise, UserName ve Password gönderilerek uzaktan login olunur. Eğer Login işlemi başarılı olunmuş ise, dönen Response modeldeki “KEY” alanı, Url parameter olarak remote şirketteki “CompanyUrl“‘de bulunan Client-Side(Angular) application’a Redirect edilir. Ve otomatik login işlemi gerçekleşmiş olur.

Buraya kadar, seçilen bir şirkete uzaktan Login olunuldu ve bu servisden geriye bir Key döndü (1). Daha sonra bu key alınarak, url parameter şeklinde gene remotedaki Client-Side Login sayfasına redirect edildi (2). Bundan sonra, ilgili şirketin Login sayfasına, alınan bu Key ile gelinince, otomatik login işlemi yapılıp Ana sayfaya yönlendirilecek ve ilgili key, Redis’den silinecekdir. (3)

Şimdi gelin isterseniz, Remote WebApi server’da neler oluyor ona bakalım.

“Geçici güvenlik için özgürlüğünü feda edenler ne özgürlüğü hak eder ne de güvenliği.” ―Benjamin Franklin

Remote LoginController/MainLogin(): Remote Login amaçlı request çekilen servis, aşağıdaki gibidir. Aslında burası, LoginServices’den hemen önceki dışarıya açık olan bir api katmanıdır.

LoginResultModel: Login olununca, client’a dönülecek olan ResultModeldir.

RedisMainLoginModel: Remote Login işleminden sonra, geçici olarak Redis’e set edilecek olan Login modeldir.

CheckMainLogin(): Bu method, kullanıcının girdiği Credentialların kontrol edilip doğruluğun onaylandığı ve buna göre Tokenların üretildiği yerdir.

  • decPassword = _encryption.DecryptText(model.Password)“: Encrypted gönderilen password, decrypt edilir.
  • if (model.Password != “” && decPassword == “”)“: Encrypted olması gereken Password’ün, fake olduğu anlamına gelmektedir. Var olan şifreleme methodu ile şifrelenmediği anlaşılmıştır.
  • var user = IsValidUserAndPasswordCombination(model.UserName, decPassword)“: Girilen UserName ve Password’ün doğru olup olmadığı kontrol edilmektedir. İlgili function, bir sonraki adımda detaylıca incelenecektir.
  • var (encToken, decToken) = _encryption.GenerateToken(user.Email); redisMainLoginModel.Token = encToken“: Girilen bilgiler doğru ise, Client için email bilgisinden yeni bir token üretilir.
  • var createTime = DateTime.Now; var cacheKey = _redisCacheService.GetTokenKey(user.IdUser, isMobile, false, model.UnqDeviceId)“: Doğru Login işleminden sonra, client’ın UserID’si, Platform tipi(Mobile,web) ve Token tipine Göre(Token,RefreshToken), redis’e atanacak bir key oluşturulur.

  •  “_redisCacheService.Set(cacheKey, decToken, createTime.AddMinutes(_coreContext.TokenExpireTime));// 1 saatlik Token Açık Atılır.“: Redise yaratılan key ile 1 saat Expire süreli Client’a özel oluşturulan “Token” atılır.

  • var refreshToken = _encryption.GenerateToken(user.Email); redisMainLoginModel.RefreshToken = refreshToken.encToken; _redisCacheService.Set(_redisCacheService.GetTokenKey(user.IdUser, false, true, model.UnqDeviceId), refreshToken.decToken, tokenExpireTime)“: Token için yapılan tüm işlemler RefreshToken için de yapılır.
  • *”string tempLoginKey = GenerateKey.GenerateStringKey(): Burada esas amaç, Remote login olan client’a login olduktan sadece 1 defaya mahsus bir keyin oluşturulmasıdır. Makelenin devamında methoda ait kodlar gösterilmektedir. Bu key sayesinde RemoteLogin olan client, username ve password bilgilerine ihtiyaç duymadan tek sefere mahsus login olabilecektir.
  • _redisCacheService.Set(tempLoginKey, redisMainLoginModel, DateTime.Now.AddMinutes(5))“: 5 dakika için geçerli olan, oluşturulan tempLoginKey ile RemoteLogin yapan Client’ın Credential bilgileri Redis‘e atılır.
  • mainLoginResultModel.Key = _encryption.EncryptText(tempLoginKey); mainLoginResultModel.CreatedTokenTime = createTime; mainLoginResultModel.UserID = user.IdUser“: Uzaktan login olan Client’a özel oluşturulan bu TempKey, UserID ve CreatedTime geri dönülür.

CheckMainLogin():

“Başlarınız, düşünceler yön değiştirebilsin diye yuvarlaktır.” ―Francis Picabia

Aşağıda Remote Login işlemi başarılı olmuş bir Client’ın, Redis üzerinde 1 kullanımlık ve 5 dakikalık bir süre için geçerli olan, “tempLoginKey” ile atılan örnek bir kaydı bulunmaktadır.

IsValidUserAndPasswordCombination: Remote Login ile uzaktan gönderilen Username ve Password’ün, kontrol edildiği methoddur.

  • var user = _userRepository.Table.FirstOrDefault(k => k.UserName.Equals(username))“: Gönderilen unique UserName’e göre, DB’den user bilgisi çekilir.
  • var encryptKey = user.PasswordHash“: Gönderilen Username’e göre bulunan User’ın Password’ü, Hash’li olarak DB’den alınır.
  • string getEncryptKey = encryptKey.Split(‘æ’)[0]“: Alınan Password içinde, key ve salt bulunmaktadır. İkisi de ayrı ayrı bulunur.
  • bool isSuccess = _encryption.ValidateHash(password, getSalt, getEncryptKey)“: Gönderilen Password aynı salt key ile Hashlenir. DB’deki kullanıcının gerçek Hashli password ile Client tarafından gönderilen sorgulanacak Password aynı key ile Hashlenerek karşılaştırılır. Aynı ise, başarılı sonucu geri dönülür.

GenerateStringKey(): Remote Login olan Client’a, tek seferlik verilen keyin oluşturulduğu methoddur.

Neden Tokenlar geri dönülmüyor da, bu oluşturulan Unique key geri dönülüyor ?

Nedeni tamamen güvenlik. Remote bir sistemden Login olunduktan sonra, geri dönülen key ile ilgili firmanin Login sayfasına gidilecek ve bu tek kullanımlık key ile login sayfası geçilerek Ana sayfaya gelinecek. Bu arada sistemler arası iletilen tek şey, Encrypt edilmiş olan bir key olacaktır.

Aşağıdaki Angular client-side kodlarda da görüldüğü gibi, dönen encrypted unique-key, remote daki Client-Side taraftaki Login sayfasına, URL parameter olarak gönderilmektedir.

Aşağıda görüldüğü gibi, remote ilgili şirket üzerinde çalışan Client-Side Angular Login Component Constructor’ında, eğer Url içerisinde “key” parametresi var ise, authService üzerindeki loginGate() methodu çağrılmaktadır.

Son olarak aşağıda görüldüğü gibi “loginGate()” servisinde, server-side tarafdaki LoginGate methoduna, ilgili “key” ile post işlemi yapılmaktadır. Böylece Remote olarak sadece bir key ile Username ve Password gönderilmeden uzaktan login olunması sağlanmıştır. İlk sefer gönderilen UserName ve Password ile tek seferlik unique bir key oluşturulmuş ve bu key, Encoded bir şekilde network ağında kullanılmıştır. İlgili key, Ana-Login sayfasına Url Parameter olarak gönderilmiş ve makalenin devamında anlatılacak LoginGate() servisi ile sanki username password girilmiş gibi ilgili Token ve RefreshToken client’a gönderilmiştir. Böylece tek bir ana noktadan Login olunarak, var olan sistemin Login sayfası atlanmıştır.

LoginGate(): Aşağıda görüldüğü gibi, bu method normal Login() methoduna karşın Remote bir sistemden Login işlemi gerçekleştirildikten sonra çağrılmaktadır. İlgili CheckLoginRedisKey() servisinden doğrulama yapılmaktadır.

CheckLoginRedisKey(): Gönderilen encoded key’e göre, credential bilgilerinin geri dönüldüğü servisdir.

  • decryptKey = _encryption.DecryptText(key)“: Encrpted gönderilen key, decrypt edilerek okunur hale getirilir.
  • RedisMainLoginModel redisMainLoginModel = _redisCacheService.Get<RedisMainLoginModel>(decryptKey)“: İlgili key ile Redis’den, “RedisMainLoginModel” çekilir.
  • if(redisMainLoginModel!= null)” : Kayıt var mı yok mu diye kontrol edilir. Her key sadece 1 kere kullanılabilir. Aynı key 2.kere kullanılmaya çalışıldığında, hata alınır.
  • var userData = _userService.GetById(redisMainLoginModel.UserId).Entity“: Redis’den çekilen UserID değerine göre, kullanıcı bilgileri Redis’den yok ise DB’den çekilir.(_userServices’de, ilgili kullanıcı bilgileri önce Redis’de var mı diye kontrol edilir, yok ise DB’den çekilerek ilgili Redis doldurulur.)
  • var loginResultModel = new LoginResultModel“: Client’a geri dönülecek, Token, RefreshToken ve UserID gibi bilgileri içeren modeldir.
  • “_redisCacheService.Remove(decryptKey)”: Redis’den ilgili credential bilgileri alındıktan sonra, silinir. Kısacası, her key ile sadece bir giriş hakkı sağlanmış olunur.

Böylece tek bir yerden, farklı sistemlere Login olunmuş olunur. Amaç kendi içinde Login sistemi olan birden fazla sisteme, dışarıdan tek bir yerden login olunulmasıdır. Bu makalede anlatılmayan bir de, mikroservis işi vardır. O da yeni bir şirketin kurulması durumunda, Global tanımlanan Companies tablosuna, ilgili şirketin Backend ve Client-Side Url adresi ile birlikte yaratılması ve oluşan yeni CompanyCode’un ilgili kullanıcılara çeşitli yollar ile bildirilmesidir.(Mail, Sms gibi..)

İlk Login işleminden sonra geriye Token yerine, oluşturulan Unique keyin encrypted dönülmesi, tamamen güvenlik sebebiyledir. 2 sistem arasında Tokenlar yerine tek seferlik, Redis’de oluşturulan bu key kullanılmaktadır. Redis bu yaklaşımda, zorunlu  bir araç olarak kullanılmakta ve maalesef bağımlılığı arttırmaktadır.

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

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. mehmet kelleli dedi ki:

    ya sen nasil bir kralsin normalde claim mantigini mvc yapisinda hic kullanmamistim ve jwt mantigini kafamda tam oturtamamistim tesekkurler :)

Bir cevap yazın

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