Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Keycloak’da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim… #12

Merhaba,

Bu içeriğimizde, UMA 2.0 standardını temel alan; klasik rol bazlı yetkilendirme mantığından çok daha güçlü bir yapı sunan policy (kural) tabanlı erişim kontrol sistemi olan Advanced Authorization Services sistemini teorik olarak ele alacak ve edineceğimiz farkındalık üzerine Asp.NET Core’da pratiksel deneyimlerde bulunacağız. O halde, konunun temellerini adım adım inceleyerek başlayalım…

Advanced Authorization Services Nedir?

Keycloak Advanced Authorization Services, uygulamalarda erişim kontrolünü basit rol kontrolünden çıkarıp dinamik, kural tabanlı ve bağlama duyarlı hale getiren gelişmiş bir yetkilendirme sistemidir.

Klasik Yetkilendirme Yaklaşımından Farkı Nedir?

Klasik yetkilendirme yaklaşımında kullanıcı sisteme giriş yaptıktan sonra yalnızca token içindeki rolüne bakılarak karar verilir… Buna örnek vermemiz gerekirse eğer; -kullanıcı admin ise izin verilsin, user ise verilmesin- gibi basit kurallar uygulanır. Ancak bu yöntem erişilen verinin ne olduğu, kime ait olduğu ya da hangi koşullarda erişildiği gibi kritik detayları dikkate almadığı için gerçek dünya senaryolarında yetersiz kalabilmektedir.

Buna karşılık Keycloak Advanced Authorization Services, UMA 2.0 temelli yapısıyla yetkilendirmeyi sadece role bağlı olmaktan çıkarıp, kullanıcının kim olduğu, hangi kaynağa erişmek istediği, bu kaynağın kime ait olduğu ve erişimin hangi şartlarda gerçekleştiği gibi dinamik faktörleri değerlendiren policy (kural) tabanlı bir sisteme dönüştürerek çok daha esnek ve güçlü bir kontrol mekanizması sağlamaktadır.

Advanced Authorization; -kim, hangi kaynağa, hangi şartlarda erişebilir?- sorununa policy (kural) ile çözüm getirmektedir.

Sistemin 4 Temel Parçası

Keycloak’un Advanced Authorization yapısında yetkilendirme; korunan varlıkları temsil eden resource, bu varlıklar üzerinde yapılabilecek işlemleri belirleyen scope, erişim kararını veren kuralları içeren policy ve tüm bunların birleşimiyle -kim, hangi kaynağa, hangi şartlarda erişebilir?- sorusuna cevap veren permission bileşenlerinden oluşmaktadır.

Parça Açıklama Örnek
Resource Korunan varlıktır. Yani sistemde erişimini kontrol etmek istediğimiz somut “şey”dir. Kullanıcıların görmesini, değiştirmesini ya da silmesini sınırlandırmak istediğimiz veri veya varlıktır. Örneğin bir sipariş kaydı, bir dosya ya da bir ayar objesi gibi düşünülebilir. Ve Keycloak bu “şey” üzerine kimin ne yapabileceğini belirlemek için onu bir resource olarak tanımlamaktadır. /orders/123
Scope Bir kullanıcının tanımlanan resource üzerinde tam olarak ne yapabileceğini ifade eden yetki tipidir. Yani mesele sadece -erişebilir mi?- değil, -erişince ne yapabilir?- sorusudur! Bu da read, write, delete ve approve gibi aksiyonlarla belirlenmektedir. Böylece aynı kaynağa farklı kullanıcılar farklı seviyelerde yetkiyle erişebilir. read, write
Policy Bir kullanıcının belirli bir resource üzerinde tanımlanan scope’u kullanıp kullanamayacağını belirleyen karar/kural mekanizmasıdır. Sistemin asıl “aklı” burada çalışmaktadır ve kullanıcı rolü, kaynağın sahibi olup olmadığı, kullanıcıya ait özellikler ve zaman gibi faktörlere bakarak erişime izin verir ya da reddeder. Hatta istenirse özel kod yazılarak bu karar tamamen ihtiyaca göre şekillendirilebilir. admin ise izin ver
Permission Resource, scope ve policy’nin bir araya gelerek oluşturduğu nihai karar yapısıdır. Sistemin -bu kullanıcı, şu kaynağa, şu işlemi yapabilir mi?- sorusuna verdiği kesin cevaptır ve esasında tek başına bir şey değildir! Bu üç bileşenin birleşimiyle oluşan ve erişime izin verilip verilmeyeceğini belirleyen nihai noktadır. admin → /orders read

UMA 2.0 Nedir? Ne Getirmektedir?

UMA 2.0 (User-Managed Access 2.0), klasik rol tabanlı yetkilendirmeden çok daha esnek ve kullanıcı odaklı bir erişim kontrol standardıdır. Temel fikir olarak; kaynak (resource) sahiplerinin, kendi kaynaklarına kimin hangi koşullarda erişebileceğini merkezi bir sistem üzerinden yönetebilmesidir. Böylece artık yetkiyi sadece admin, user gibi statik rollere değil, dinamik kurallara ve koşullara bağlı olarak vermeyi sağlamaktadır.

UMA 2.0 ile gelen temel yenilikler şunlardır:

  • Kullanıcı Kontrolü
    Kaynak sahibi, kendi verisine kimlerin erişebileceğini yönetebilir.
  • Koşul Tabanlı Erişim
    Saat, IP, cihaz, kullanıcı geçmişi vs. gibi parametreler izin kararına dahil edilebilir.
  • Merkezi Yetkilendirme
    API veya uygulama client’ları, merkezi bir izin sunucusuna (Keycloak gibi) erişim izni sorarak davranışlarını şekillendirebilir.
  • Dinamik Token (RPT – Requesting Party Token)
    Erişim isteği sırasında politika ve kurallar değerlendirilir, sonucu belirten özel token oluşturulur.
  • Çoklu Sistem Entegrasyonu
    Kullanıcı yahut kaynak bilgisi farklı sistemlerde gelebilir. UMA bu tarz heterojen ortamlara da uyum sağlayabilir.

Kısaca UMA 2.0; -kim, neye, ne zaman, hangi koşullarda erişebilir?- sorularını dinamik ve merkezi olarak yönetebilmemizi sağlayan ve özellikle çok kullanıcılı, çok sistemli ve hassas veri ortamlarında kritik bir esneklik ve güvenlik sağlayan bir standarttır.

Nasıl Çalışmaktadır?

Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12UMA 2.0’ın akış mantığı yukarıdaki görselde olduğu gibi cereyan etmektedir:

  1. İstek: Client, API’ye istek atar.
  2. Soru: API, Keycloak’a -bu kullanıcı bu resource’a erişebilir mi?- diye sorar.
  3. Policy Kontrol: Keycloak, policy’leri çalıştırır ve karar üretir.
  4. Sonuç: Nihai olarak erişimi izin verilir yahut engellenir.

Tabi bu şema client bazlı mantığı yansıtmaktadır. Bir yandan kullanıcı bazlı mantığı da ele alırsak;Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12şeklinde bir akış düşünebiliriz. Burada süreci gözlemlersek;

  1. Kullanıcı login olup, access token alır.
  2. Ardından API’ye gidip -ben bu resource’a erişmek istiyorum- der.
  3. Keycloak gerekli kontrolleri sağlar ve izin var mı yok mu değerlendirir. Eğer izin yoksa permission ticket üretir, varsa da ticket’a gerek kalmaksızın RPT üretilir.
    Permission Ticket Nedir?
    Bu ticket, -bu kullanıcı şu resource’a erişmek istiyor- bilgisini taşıyan geçici bir izin talebi belgesidir. Keycloak, bu ticket ile client tekrar geldiği taktirde izin sürecini başlatmaktadır.

    Burada -izin yoksa direkt reddet geç- mantığında bir davranış bekleyebilirsiniz. Ancak UMA 2.0’ın amacı klasik sistemlerden farklı olarak reddetmek odaklı değil, izin sürecini yönetmek odaklıdır. Klasik sistemlerde izin yoksa eğer 403 status code’uyla süreç sonlandırılır. Ancak UMA 2.0’da izin olmasa dahi belki alınabileceği düşünüldüğü için süreç başlatılır. Nasıl belki alınabilir hoca la… diye sorarsanız, düşünün… Kaynak sahibi belki izin verecektir, belki de erişimi admin onaylayacaktır ya da sistem koşullara göre izni sağlayacaktır. Haliyle bu şekilde opsiyonel olan bir süreci değerlendirmek, evet, adı üzerinden bir süreç başlatmayı gerektirmektedir. Bunu da yukarıda söylendiği gibi permission ticket ile gerçekleştirmekteyiz.

    Tabi permission ticket davranışı;

    • kullanıcılar arası veri paylaşımı süreçlerinde,
    • dosya / belge sistemlerinde,
    • sosyal platformlarda,
    • AI agent’ların kullanıcılar adına işlem yaptığı süreçlerde,
    • ve dinamik izin akışlarında

    oldukça mantıklıdır.

    Bunların dışında tabi ki de basit CRUD API’lerinde yahut sadece admin veya user gibi basit ayrımların ve statik rollerin olduğu sistemlerde kullanımı oldukça gereksizdir!

  4. Client, üretilen permission ticket ile tekrar Keycloak’a gider.
  5. Keycloak, policy’leri çalıştırır ve uygunsa RPT üretir.
    RPT (Requesting Party Token) Nedir?
    RPT, UMA 2.0 kapsamında üretilen, kullanıcının belirli bir resource’a hangi izinlerle erişebileceğini gösteren özel bir access token’dır. Yani -bu kullanıcı şunları yapabilir- bilgisini taşıyan token’dır. Ama dikkat edin ki bir access token değildir! Access token ile RPT evet… birbirlerine benzemektedirler, ikisi de JWT’dir, evet… ikisi de Keycloak tarafından üretilir, evet… her ikisi de request süreçlerinde authorization header’ı üzerinden API’lere gönderilir… Ancak bilinmelidir ki farklıdırlar 🙂 Access token’ın amacı kimlikken (authentication), RPT’nin yetkidir (authorization). Access token, -ben kimim- sorusuna cevap verirken, RPT ise -ne yapabilirim- sorusuna cevap vermektedir. Haliyle bir çalışmada access token olmaksızın RPT alınması mümkün değildir!

UMA 2.0’ı Kullanırken Nelere Dikkat Etmek Gerekmektedir?

UMA 2.0 doğru kullanılmadığı taktirde aşırı karmaşık bir canavara dönüşebilir. Bu nedenle konuya ilişkin pratik ve net uyarılarla dikkat edilmesi gereken hususlara dair farkındalık oluşturmak önem arz etmektedir.

  • UMA 2.0 kullanılacaksa gerçekten gerekli mi sorusu sorulmalıdır!
    UMA 2.0 kullanırken en kritik mesele, gerçekten buna ihtiyaç olup olmadığını doğru değerlendirmektir. Çünkü basit CRUD işlemleri, klasik admin/user ayrımı veya statik rol yapıları olan bir sistemde UMA gereksiz karmaşa ve performans maliyeti getirecektir. Buna karşılık kullanıcılar arası veri paylaşımı, dinamik izin talepleri ve onay süreçleri, AI agent’larının kontrollü aksiyon alması veya multi-tenant ve karmaşık erişim kuralları gibi durumlarda ciddi bir esneklik ve güç sağlayacaktır. Bu yüzden yanlış yerde kullanıldığında sistemi yavaşlatan bir yüke dönüşebilme ihtimali söz konusudur.
  • Resource tasarımı doğru yapılmalıdır!
    UMA 2.0 kullanırken resource tasarımını doğru yapmak kritik öneme sahiptir. Çünkü, her bir veri kaydını ayrı bir resource olarak tanımlamak sistemde kontrol edilmesi gereken nesne sayısını aşırı artıracağı için ciddi performans problemlerine yol açabilir. Bunun yerine benzer varlıkları mantıklı seviyede gruplayarak daha kaba (coarse-grained) bir yapı kurmak hem yönetilebilirliği artırır hem de yetkilendirme süreçlerinin daha hızlı ve verimli çalışmasını sağlar.
  • Scope’lar minimal tutulmalıdır!
    Scope’ları mümkün olduğunca sade ve az sayıda tutmak gerekmektedir. Her aksiyonu ayrı ayrı tanımlamak sistemi gereksiz karmaşık hale getirecek ve yönetimi zorlaştıracaktır. Bunun yerine read, write, delete gibi temel aksiyonlarla ilerleyip detaylı ayrımları policy tarafında çözmek hem daha esnek hem de daha sürdürülebilir bir yetkilendirme yapısı sağlayacaktır.
  • Policy’ler sade ve deterministik olmalıdır!
    Keycloak üzerinde UMA 2.0 kullanırken policy’leri mümkün olduğunca sade, küçük ve deterministik tutmak gerekmektedir. Çünkü tüm mantığı tek bir karmaşık policy içinde toplamak, özellikle JavaScript tabanlı kuralların, dış sistem çağrılarının ve iç içe geçmiş şartların olduğu senaryolar sistemi hem anlaşılmaz hem de zor debug edilebilir hale getirecektir. Bunun yerine her policy’nin tek bir işi yaptığı, mümkün olduğunca token içinde claim’lere dayanan ve birbirinden bağımsız çalışan bir yapı kurmak hem performans artıracak hem de yetkilendirme kararlarının öngörülebilir ve yönetilebilir olmasını sağlayacaktır.
  • Token’ın şişmesine dikkat edilmelidir!
    UMA 2.0 kullanırken RPT içinde taşınan resource ve scope bilgilerinin gereksiz yere çoğaltılması token’ın boyutunu büyüterek hem ağ üzerinden taşınan veri miktarını artırır hem de her istekte doğrulama maliyetini yükselterek performansı olumsuz etkiler. Bu yüzden mümkün olduğunca daha genel (coarse-grained) resource tanımları yapmak ve sadece gerçekten gerekli izinleri token’a dahil etmek sistemin daha hızlı ve verimli çalışmasını sağlayacaktır.
  • Performans önemsenmelidir! Hafife alınmamalıdır!
    UMA 2.0 kullanırken performans hafife alınmamalıdır. Çünkü bu modelde klasik akışa ek olarak access token, permission ticket ve RPT üretimi gibi ekstra istek-cevap (roundtrip) süreçleri söz konusu olacaktır ve bunların her API çağrısında tekrar etmesi sistemi ciddi şekilde yavaşlatacaktır. Bu yüzden RPT’yi cache’lemek, her istekte tekrar Keycloak’a gitmemek ve backend tarafında kısa süreli önbellekleme stratejileri kullanmak performans açısından kritik bir optimizasyondur.
  • Stateless ve Stateful farkını iyi anlamak gerekmektedir!
    UMA 2.0 kullanırken klasik JWT yapısının tamamen stateless olmasına karşılık UMA’nın permission ticket, policy evaluation ve RPT üretimi gibi süreçlerle kısmen stateful bir yapıya geçtiğini iyi anlamak gerekmektedir. Çünkü bu durum sistemin davranışını daha dinamik hale getirirken aynı zamanda hata ayıklamayı zorlaştıracak ve kararların sadece token içeriğine değil, anlık değerlendirme süreçlerine de bağlı olmasına neden olacaktır.
  • Debugging planının hazır kıta olması faydalı olacaktır!
    UMA 2.0 kullanırken en zor konulardan biri -neden izin verilmedi?- sorusuna cevap aramaktır… Çünkü kararlar sadece token’a bakılarak değil, policy’lerin dinamik olarak çalıştırılmasıyla verilmektedir. Bu yüzden Keycloak üzerinde log’ları aktif etmek, policy evaluation araçlarını kullanmak ve farklı senaryoları test edebileceğin örnek kullanıcılar oluşturmak, hataları hızlı teşhis edebilmek ve sistemin nasıl karar verdiğini anlayabilmek açısından kritik öneme sahiptir.
  • Client’a güvenilmemeli, tüm yetkilendirme kontrolleri backend tarafından doğrulanmalıdır!
    UMA 2.0 kullanırken en kritik güvenlik noktalarından biri client’a asla güvenilmemesi ve tüm yetkilendirme kontrollerinin backend tarafında doğrulanması gerekliliğidir. Özellikle RPT içindeki izinlerin gerçekten geçerli olup olmadığını sunucu tarafında kontrol etmek ve yalnızca token’ın varlığına değil, içindeki scope’ların ilgili işlem için uygun olup olmadığına bakmak gerekmektedir! Aksi taktirde client manipülasyonları veya eksik kontroller ciddi güvenlik açıklarına sebebiyet verebilir.

13 Kritik Soru / 13 Kritik Cevap

Şimdi konuyu daha iyi anlayabilmek ve duvar köşelerindeki kırıntılara kadar sindirebilmek için akla gelen gelmeyen tüm soruları burada masaya yatırıp cevaplandırmaya çalışalım:

Permission Explosion nedir?
Permission Explosion (İzin Patlaması), Keycloak gibi sistemlerde her kullanıcı, her resource ve her işlem için ayrı ayrı izin tanımlandığında ortaya çıkan kontrolsüz büyüme problemidir. Bu durumda permission sayısı katlanarak artar, sistem yönetilemez hale gelir, performans düşer ve token’lar şişer. Bu yüzden doğru yaklaşım her ihtimal için ayrı izin üretmek yerine daha genel resource ve scope’lar tanımlayıp erişim kararını dinamik policy’lerle vermektir.
Soru 1 | UMA 2.0 ve normal access token arasındaki fark nedir?

Kısaca ifade etmek gerekirse, UMA 2.0 kapsamında access token ve RPT birbirini tamamlayan iki farklı roldedir diyebiliriz. Access token, kullanıcının kim olduğunu kanıtlar, yani sisteme -ben şu kullanıcıyım- diyerek bir kimlik doğrulama sağlar. Ancak kimliğin doğrulanması tek başına bir kaynağa erişim izni vermeyeceğinden yetkilendirme süreçlerinde yeterli değildir. Kullanıcı, belirli bir resource’a erişmek isterse, sistem bu talebi ayrıca değerlendirir ve uygun görürse RPT üretir. RPT, artık kullanıcının hangi kaynağa, hangi işlemlerle erişebileceğini açıkça belirten yetkiyi temsil eder. Bu yüzden akışta önce access token alınır, ardından gerekirse RPT üretilir ve böylece gerçek yetkilendirme kararı RPT üzerinden sağlanmış olur.

Soru 2 | Permission Ticket neden üretilir?

İzin yoksa neden permission ticket üretiliyor?

UMA 2.0 içinde izin yokken permission ticket üretilmesinin sebebi, isteği direkt reddetmek yerine bir izin talebi süreci başlatmaktır. Yani kullanıcı o anda yetkili olmasa bile sistem bu talebi kayıt altına alır, resource sahibi ya da ilgili policy’ler bu talebi sonradan değerlendirebilir ve eğer uygun görülürse kullanıcı tekrar geldiğinde RPT alarak erişim kazanabilir. Böylece sistem statik izin yoksa süreci 403 ile sonlandır yaklaşımı yerine daha dinamik ve yönetilebilir bir yetkilendirme akışı sunmuş olur.

Soru 3 | Resource ve Scope kavramları nasıl tasarlanmalıdır?

Resource scope tasarımı mümkün mertebe sade ve dengeli olmalıdır. Resource’lar her bir veri kaydını tek tek temsil edecek kadar detaylı değil, mantıklı gruplar halinde tanımlanmalı, scope’lar ise gereksiz çeşitliliğe kaçmadan read, write, delete gibi temel aksiyonlarla sınırlandırılmalıdır. Çünkü bu yaklaşım, hem token’ların şişmesini engeller hem de policy’lerin daha anlaşılır, yönetilebilir ve performanslı çalışmasını sağlar.

Soru 4 | Policy oluştururken nelere dikkat etmeliyiz?

Policy tasarlarken kuralların mümkün olduğunca basit, deterministik ve tek bir amaca odaklı olması tavsiye edilmektedir. Her policy tek bir işi çözmeli, mümkünse token içinde claim’ler üzerinden çalışmalı ve karmaşık işlemlerden mümkün mertebe kaçınmalıdır. Yani anlayacağınız klasik yazılımsal ilkelerdeki hassasiyet, policy’lerde de geçerliliğini korumaktadır.

Soru 5 | UMA’yı hangi durumlarda kullanmak mantıklıdır?

UMA 2.0 özellikle kullanıcılar arası veri paylaşımının olduğu ve izinlerin statik rollerle değil dinamik süreçlerle belirlendiği durumlarda daha anlamlıdır. Örneğin, bir kullanıcının kendi dosyasını başkalarıyla paylaşabilmesi, erişim talebinde bulunulup sonradan onay verilmesi veya yetkilerin geçici olarak devredilmesi gibi senaryolarda UMA ciddi bir esneklik sağlamakta, ancak admin/user gibi basit ayrımların yeterli olduğu sistmelerde gereksiz karmaşıklığa sebebiyet vermektedir.

Soru 6 | Her request’te yetkilendirmeyle ilgili durumu Keycloak’a mı soracağım?

Hayır, bu sistemi öldürür. UMA’da alınan RPT cache’lenmeli ve mümkün olduğunca backend tarafında doğrulanmalıdır. Aksi halde her istekte ekstra roundtrip performans darboğazı kaçınılmazdır!

Soru 7 | RPT’yi doğrulamazsak ne olur?

Sistem çöker 🙂 Çünkü direkt client tarafına güvenilmiş olunur. Bu yüzden RPT mutlaka backend tarafında validate edilmeli ve içindeki permission’lar gerçekten kontrol edilmelidir.

Soru 8 | Permission Explosion (İzin Patlaması) nasıl önlenir?

Her resource + scope kombinasyonunu ayrı ayrı üretirseniz sistem çöker. Bunun yerine genelleştirilmiş resource’lar + dinamik policy’ler kullanılmalıdır. Tabi bu durumun pratik düzlemde daha net anlaşılacağı aşikardır 😉

Soru 9 | UMA gerçekten gerekli mi, yoksa overengineering mi?

Eğer sistemde kullanıcılar arası etkileşim yoksa ve izinler statikse yüksek ihtimal overengineering yapılmaktadır. UMA 2.0 ancak dinamik yetki akışı olan senaryolar için anlamlıdır.

Soru 10 | Authorization ile business logic sınırı nerede başlamalı, nerede bitmelidir?

En kritik ayrımlardan birisi de budur diyebiliriz. Keycloak sadece -erişebilir mi?- sorusunu cevaplamalıdır… -Ne yapmalı?- sorusu ise uygulama logic’inde kalmalıdır… Aksi taktirde tüm iş kuralları Keycloak’a gömülürse sistem kontrol edilemez hale gelecektir.

Soru 11 | Eventual consistency problemi yaşanılır mı?

Muhtemelen… Çünkü token (özellikle RPT) üretildikten sonra içindeki izinler bir süre geçerli kalacaktır. Bu yüzden bir kullanıcıdan izin kaldırılsa bile eski token ile bir süre erişim devam edecektir. Bunu çözmek için token ömrü kısa tutulmalı ve re-check mekanizması kurulmalıdır.

Soru 12 | Revocation (izin geri alma) nasıl yönetilir?

UMA’da bu kritik bir problemdir. İzin geri alındığında eski RPT’ler hala geçerli olabilir, bu yüzden ya token süresi kısa tutulmalı ya da kritik işlemlerde backend tarafında ekstradan kontroller sağlanmalıdır.

Soru 13 | Policy değişirse mevcut RPT’ler ne olacak?

Aslında bunu önceki sorularda cevaplandırmış bulunuyoruz… RPT, verilmiş bir izne karşılık gelmektedir. Haliyle policy değişse bile token süresi dolana kadar geçerliliğini koruyacaktır. Bu yüzden sürekli vurguladığımız gibi kısa ömürlü token kullanmanın yanında gerektiği noktalarda token invalidation uygulanması da gerekmektedir.

Keycloak + Asp.NET Core → Policy-Based Authorization

Evet… Artık konuya dair pratik dokunuşlara geçebiliriz. Tabi ilk olarak Keycloak eşliğinde Asp.NET Core’da Policy-Based Authorization‘ı ele alalım istiyorum. Bunun nedeni, Asp.NET Core’daki politika yapılanmasının statik veya yarı-dinamik kontroller eşliğinde yetkilendirme davranışını sağlıyor oluşudur. Bu yaklaşımda, token’dan gelen bilgilerle belirli davranışsal şartlar kontrol edilerek endpoint’lere erişim sınırlandırmaları getirilebilmektedir. Şöyle ki; application-client adında Direct access grants akışını destekleyen bir client’ımız olsun. Bu client’tan alınan token ile aşağıdaki gibi bir politika tabanlı kontrol sağlanabilir.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.Authority = "http://127.0.0.1:8080/realms/master";
        options.Audience = "account";
        options.RequireHttpsMetadata = false;

        options.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                var identity = (ClaimsIdentity)context.Principal!.Identity!;
                var realmAccess = context.Principal.FindFirst("realm_access")?.Value;

                if (realmAccess != null)
                {
                    var doc = JsonDocument.Parse(realmAccess);
                    if (doc.RootElement.TryGetProperty("roles", out var roles))
                        foreach (var role in roles.EnumerateArray())
                            identity.AddClaim(new Claim(ClaimTypes.Role, role.GetString()!));
                }

                return Task.CompletedTask;
            }
        };

        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            RoleClaimType = ClaimTypes.Role,
            NameClaimType = ClaimTypes.Name
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireAuthenticatedUser()
              .RequireClaim(ClaimTypes.Role, "admin");
    });
});
.
.
.

Tabi burada, 36 ile 43. satır aralığında oluşturulan politika gereği payload’daki realm_access alanındaki rollerin değerlendirilmesi gerekmektedir. Ancak, mimaride bir obje içerisindeki dizin içinde arama yapacak kısa yol olmadığı için 12 ile 27. satır aralığında ilgili alandaki roller elde edilerek identity’e ClaimTypes.Role type’ı karşılığında verilmektedir, ki AdminOnly politikasında, istek gönderen kullanıcının admin rolüne sahip olup olmadığı değerlendirilebilsin.

Burada ekstradan 12 ile 27. satır aralığındaki claim ayıklama işlemini aşağıdaki gibi harici bir transformation sınıfında yürütebilir ve böylece daha temiz bir çalışma gerçekleştirebiliriz.

    public class KeycloakRolesTransformer : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            var identity = (ClaimsIdentity)principal.Identity!;
            var realmAccess = identity.FindFirst("realm_access");

            if (realmAccess is not null)
            {
                var json = JsonDocument.Parse(realmAccess.Value);
                if (json.RootElement.TryGetProperty("roles", out var roles))
                    foreach (var role in roles.EnumerateArray())
                    {
                        var r = role.GetString();
                        if (!string.IsNullOrEmpty(r))
                            identity.AddClaim(new Claim(ClaimTypes.Role, r));
                    }
            }

            return Task.FromResult(principal);
        }
    }
builder.Services.AddScoped<IClaimsTransformation, KeycloakRolesTransformer>();

İşte bu kadar…

Keycloak + Asp.NET Core → Resource-Based Authorization (RBA)

Şimdi de esas konumuz olan UMA 2.0’ın davranışına daha yakın olan Resource-Based Authorization’ı tecrübe edelim. Tabi öncelikle ne olduğunu izah edelim…

Resource-Based Authorization Nedir?
Resource-Based Authorization’ın ne olduğunu anlayabilmek için öncelikle normal authorization yapılanmasını anlamlandırmamız gerekmektedir. Malumunuz, normal authorization -bu user admin mi?- şeklinde soru sorar ve evet/hayır mantığında davranış sergiler. Resource-Based Authorization’da ise sorunun mahiyeti biraz değişecektir ve -bu user bu spesifik kaynağa (resource) erişebilir mi?- formuna bürünecektir. Bu kaynak kullanıcının kendi oluşturduğu postu ya da siparişi olabilir. Yani anlayacağınız, erişilmesi gereken resource bazlı bir koşul ortaya koyulması gereken senaryolarda bu yaklaşım oldukça idealdir.

Şimdi bu mantıkta bir authorization çalışması için aşağıdaki gibi yapıların tanımlanması gerekmektedir:

  • Requirement oluşturulmalıdır
    Bu sağlanması beklenen koşulu temsil eden bir sınıftır. Ya da bir başka deyişle kuralın adıdır. Teknik olarak IAuthorizationRequirement interface’inden implemente edilmesi gerekmektedir.

        public class EditPostRequirement : IAuthorizationRequirement
        {
        }
    

    Bu sınıfın içeriğinin dolu olması elzem değildir. Zahiren bakıldığı zaman bir kullanıcının post edit’leyebilmesi için sağlaması gereken şartı ifade ettiği görülmektedir. Ki unutulmamalıdır ki requirement sınıflarında hiçbir logic bulunmamaktadır!

    Asıl iş handler sınıfındadır…

  • Handler oluşturulmalıdır
    Handler, bir requirement’ın gerçekten sağlanıp sağlanmadığını kontrol eden karar mekanizmasıdır. AuthorizationHandler<TRequirement, TResource> abstract class’ından implemente edilmesi gerekmektedir.

        public class EditPostHandler : AuthorizationHandler<EditPostRequirement, Post>
        {
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EditPostRequirement requirement, Post resource)
            {
                //ClaimTypes.NameIdentifier : sub
                if (resource.OwnerId.ToString() == context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value)
                    context.Succeed(requirement);
    
                return Task.CompletedTask;
            }
        }
    

    Burada görüldüğü üzere, resource olan Post’un editlenme sürecinde ilgili kullanıcıya ait olup olmadığı EditPostRequirement‘ın özelinde değerlendirilmektedir.

  • IoC Container’a eklenmelidir
    Handler sınıfının işlevsel olabilmesi için IoC Container’a eklenmesi gerekmektedir.

    builder.Services.AddScoped<IAuthorizationHandler, EditPostHandler>();
    
  • Ve gereken noktada kullanılmalıdır
    Resource-Based Authorization, çoğunlukla ne Authorize attribute’u ile ne de RequireAuthorization metodu ile değil çoğunlukla kod içerisinde (imperative) kontrol sağlamaktadır.

    Şöyle ki; Authorize ve RequireAuthorization yapıları, request bazlı çalışmakta ve sadece User üzerinden karar vermektedirler. Haliyle resource nedir bilmezler. Resource-Based Authorization’da ise hem user hem resource birlikte değerlendirilmekte ve kontrol runtime’da, veri çekildikten sonra gerçekleştirilmektedir. Bu yüzden genelde

    await _authorizationService.AuthorizeAsync(User, resource, requirement);

    şeklinde kullanılmaktadır. Dolayısıyla aşağıdaki gibi bir kullanım durumu söz konusu olacaktır:

    app.MapPut("/post-edit", async (IAuthorizationService authorizationService, HttpContext httpContext) =>
    {
        Post post = new()
        {
            OwnerId = new Guid("af3787e6-1c79-4ba4-a935-442851a4c529")
        };
    
        var result = await authorizationService.AuthorizeAsync(httpContext.User, post, new EditPostRequirement());
    
        if (!result.Succeeded)
            return Results.Forbid();
    
        return Results.Ok();
    });
    

    Ama burada kritik bir nokta var. O da Authorize ve RequireAuthorization yapılarının yine de gözden kaçırılmaması, yani kullanılması gerekliliğidir. Bu yapılar, kullanıcının login olup olmadığını garanti eder. Böylece zaten token’ı olmayan bir kullanıcının endpoint’e erişememesi sağlanarak ideal davranış gösterilmiş olur. Ha kimliği doğrulanmış kullanıcının bu kaynağa erişip erişmeyeceği ise artık bu kontrolün tekelindedir…

Evet… Resource-Based Authorization’da bundan ibarettir…

Keycloak + Asp.NET Core → UMA 2.0

Artık nihai olarak esas konumuzla pratik yüzleşmeye geldik diyebiliriz…

Yukarıdaki Resource-Based Authorization yaklaşımında kimin hangi kaynağa erişeceği belirlenmektedir. Bunu yaparken, kurallar kodda ya da policy’de olacak şekilde backend’de tanımlama gerçekleştirdik. Yani tüm kontrol tamamen yazılan authorization logic’te tutulmaktadır.

UMA 2.0’da ise kaynak sahibi, kimin neye erişebileceğine kendisi karar verecektir. Bunda ise yetkiyi uygulama değil, resource owner yönetecektir. Haliyle dinamik olarak yetki değişikliği ve kontrolü sağlanabilir.

Resource-Based Authorization

  • Bu kullanıcı bu document’in owner’ı mı?
  • Bu user bu resource’a ait mi?
  • Bu token’a sahip kişi, şu scope’lara erişebilir.
coarse-grained, yani daha az detay, daha genel bir yaklaşım…

UMA 2.0

  • Ahmet bu dosyayı görebilir.
  • Mehmet sadece okuyabilir.
  • Şuayip, Hilmi’nin “123” numaralı faturasına (resource’una/kaynağına) okuma yapabilsin mi?
fine-grained, yani daha fazla detay, daha hassas yaklaşım…

OAuth 2.0’da kararı sunucu verirken, UMA’da ise kaynak sahibi verir, Keycloak ise bu kararı uygular.

Şimdi UMA 2.0’ı uygulayabilmek için adım adım hem Keycloak’da hem de uygulama tarafında hassas çalışmalar gerçekleştirmemiz gerekmektedir. Bu yetkilendirme yaklaşımını tam olarak kurgulayabilmek için burada yapacağımız yapılandırmalar oldukça önem arz etmektedir.

Tabi bu yapılandırmalara geçmeden önce UMA’nın getirdiği konseptteki terminolik kavramları masaya yatırmakta fayda görmekteyim;

Kavram Açıklama
Resource Owner Kaynağın sahibi (örn: Hilmi, kendi faturalarının sahibi)
Resource Server (RS) API’nin kendisi — kaynakları koruyan sunucu
Authorization Server (AS) Keycloak — kimin neye erişebileceğine karar veren

Bunların yanında UMA’yı uygularken her adımda karşımıza çıkabilecek üç token yapısını da tekrar özetleyip notumuzu alalım;

Token Açıklama
PAT (Protection API Token)
  • Resource Server’ın (API’nin) Keycloak’a -ben buyum- demek için kullandığı token’dır.
  • API, Keycloak’a resource kaydetmek için bu token’ı kullanmaktadır.
  • Client Credentials grant ile alınır (kullanıcı yok, makine-makine)
Permission Ticket
  • -Bu kaynağa erişmek istiyorum- talebinin makbuzudur.
  • RS (API), client’a -git Keycloak’tan bununla izin al- der.
  • Tek kullanımlıkdır, kısa ömürlüdür.
RPT (Requesting Party Token)
  • Permission Ticket’ı Keycloak’a götürünce alınan asıl yetki token’ıdır.
  • İçinde hangi kaynağa hangi scope ile erişilebildiği yazmaktadır.
  • API bunu doğrular ve erişime izin verir ya da vermez.

Hadi başlayalım…

  • Adım 1 (Resource Server Client Oluşturma)
    İlk olarak Asp.NET Core uygulamamız için Keycloak’da bir kimlik edasında client oluşturacağız. Keycloak bu client üzerinden kaynak ve izin tanımlarını tutacaktır.Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Oluşturulacak bu client’ın ayarları aşağıdaki gibi olacaktır;Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Buradaki yapılandırmalardan bahsetmemiz gerekirse eğer;

    • Client authentication: Bu açık olduğu sürece client Keycloak’a kendini tanıtmak mecburiyetindedir. Client ID ve Client Secret değerlerini üretir. Haliyle bir confidential client üretecektir.
    • Authorization: Bu ayar, Keycloak’da Authorization Services’ın kapsamında olan policy, permission vs. gibi özellikleri açmaktadır. UMA 2.0 için şart bir yapılandırmadır!
    • Standard flow: Authorization Code Flow‘u sağlar.
    • Direct access grants: Resource Owner Password Credentials‘ı sağlar.

    Sonra aşağıdaki yapılandırmalarla client oluşturulmalıdır.

    Root URL: https://localhost:7086
    Home URL: https://localhost:7086
    Valid redirect URIs: https://localhost:7086/*
    Web origins: *
  • Adım 2 (Authorization Ayarlarını Yapılandırma)
    Şimdi de oluşturduğumuz client’ın authorization ayarlarını yapılandıracağız. Yani bir başka deyişle, sadece kimlik doğrulama (login) değil, aynı zamanda kullanıcının neye erişebileceğinin sorusunu da yönetiyor olacağız.

    Bunun için oluşturduğumuz client’ın detaylarından ‘Authorization’ sekmesine gelelim ve aşağıdaki gibi yapılandırmada bulunalım;Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12

    • Policy enforcement mode (Politika Uygulama Modu): Bu ayar politika uygulama modunu ayarlamamızı sağlamaktadır. Görüldüğü üzere üç seçenekten oluşmaktadır;
      • Enforcing (Zorlayıcı): En güvenli moddur. Policy yoksa yahut var olan policy şartları sağlanmadıysa erişim reddedilecektir.
      • Permissive (Esnek): Eğer bu erişimi kontrol eden hiçbir policy yoksa varsayılan olarak izin verecektir. Ancak policy var ve açıkça şartları sağlanmıyorsa o taktirde erişim engellenecektir.
      • Disabled (Kapalı): Authorization’ı tamamen devre dışı bırakacaktır. Keycloak sadece login yapacaktır ancak yetki kontrolü yapmayacaktır.
    • Decision strategy (Karar Stratejisi): Birden fazla policy’nin söz konusu olduğu senaryolarda son kararın nasıl verileceğini belirleyen stratejiyi yapılandırmaktadır.
      • Unanimous (Oybirliği): Tüm policy’lerin şartları sağlanırsa erişim olacaktır. Aralarından bir tane bile ‘deny’ varsa erişim reddedilecektir.
      • Affirmative (Çoğunluk/En az biri yeter): En az bir policy izin verirse erişim sağlanacaktır. Diğerleri reddetse bile izin çıkabilir. Esnek senaryolarda geçerlidir.
    • Remote resource management: Bu yapılandırma açık olduğu sürece uygulama (API vs.) Keycloak’a bağlantı sağlayarak resource’ları, policy’leri ve permission’ları dinamik olarak yönetebilecektir.
  • Adım 3 (Scope Tanımlama)
    UMA’da scope kavramı, bir kaynak üzerinde yapılabilecek işlem türünü ifade etmektedir.


    UMA 2.0’daki Scope vs Client Scope

    UMA 2.0’daki scope, authorization scope’dur. User-Managed Access (UMA) içinde kullanılan bir kavramdır. Bir kaynak üzerinde yapılabilecek aksiyonu temsil etmektedir ve -bu kullanıcı bu kaynağı ne yapabilir?- sorusunu cevaplar…

    Client scope ise Keycloak içinde OAuth/OIDC tarafına aittir ve token içine hangi bilgilerin (claim’lerin) ekleneceğini belirlemektedir.

    UMA Scope → yetki
    Client Scope → veri

    Haliyle bizler yine ilgili client’ın ‘Authorization’ → ‘Scopes’ kombinasyonuna gelerek aşağıdaki gibi scope’ları oluşturalım:Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12

  • Adım 4 (Resource Tanımlama)
    Şimdi de Asp.NET Core uygulamamızda hangi kaynağı koruma altına almak istiyorsak onu resource olarak tanımlayacağız. Bunun için ‘Authorization’ → ‘Resources’ kombinasyonuna gelerek aşağıdaki gibi bir resource tasarlayalım:Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Buradaki yapılandırmaya bakarsanız Asp.NET Core’daki /api/documents/{id} endpoint’i ile bu resource’u eşleştirerek, Keycloak’a hangi kaynağı koruması gerektiğini ifade etmiş oluyoruz. Ayrıca ‘Authorization scopes alanında görüldüğü gibi oluşturduğumuz scope’ları bu resource’la ilişkilendirerek yetkisel davranışları da belirlemiş oluyoruz.

    Burada ayrıca ‘Type’ alanındaki garip urn:uma-resource-server:resources:document değerine dikkatinizi çekmek istiyorum. Bu format, gözünüzü korkutmasın… diğer resource’larla bir çakışma olmaması için bu şekilde bir format benimsenmektedir. Özellikle farklı ekiplerin söz konusu olduğu microservice mimarilerde resource’ların çakışması çok tecrübe edildiği için bu tarz bir isimlendirme tercih edilmektedir.

    ‘User-Managed Access enabled’ alanı ise resource sahibinin (user) erişim kararlarına doğrudan dahil olacağını belirlemektedir. Yani yetkiyi kullanıcı yönetmektedir.

  • Adım 5 (Role & User Policy’leri Tanımlama)
    Evet… Artık sıra bir policy tanımalaya gelmiştir. Policy, -kim erişebilir?- sorusunun cevabıdır… Bunun için de ‘Authorization’ → ‘Policies’ sekmesine gelinmesi gerekmektedir.

    Policy, aşağıdaki gibi birkaç türü olan bir yapıya sahiptir:

    Policy Türü Açıklama Ne Zaman Kullanılır?
    Client Belirli bir uygulamanın (client) erişmesine izin verir. Ya da bir başka deyişle, belirli bir client üzerinden gelen taleplere izin verir. Sadece belirli servisler/API’ler erişebilsin istiyorsan
    Client Scope Token içindeki client scope’a göre erişim verir. Token’da belirli scope varsa izin ver senaryosu
    Group Kullanıcının ait olduğu gruba göre karar verir. Departman / organizasyon bazlı yetkilendirme
    Regex Belirli bir pattern (regex) eşleşirse izin verir. Dinamik veya string bazlı kurallar gerektiğinde
    Role Kullanıcının rolüne göre erişim kontrolü yapar. En yaygın: admin, user, manager gibi roller
    Time Belirli zaman aralıklarında erişime izin verir. Mesai saatleri, kampanya süresi vb.
    User Belirli kullanıcı(lar)a özel erişim verir. Spesifik kişilere izin vermek istediğinde

    Biz burada Role-Based ve User-Based olmak üzere iki policy uygulayacağız. Haliyle ilk olarak Role-Based policy’le başlayalım…

    Role-Based Policy;
    Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Burada dikkat edilirse bu politikaya uygun kullanıcıların sadece ‘user’ rolüne sahip olup olmaması yeterli görülmektedir. (Tabi önceden Realm roles sekmesinden ‘user’ rolünün oluşturulmuş olması gerekmektedir.) Burada Fetch Roles, kullanıcının alt (inherited) rollerinin de hesaba katılıp katılmamasını yapılandırmaktadır. Yani bu yapılandırma kapalı kaldığı taktirde sadece direkt atanmış roller kontrol edilecektir. Açılırsa eğer composite (iç içe) roller de dahil edilecek ve böylece rol hiyerarşisi de önem kazanmış olacaktır. Logic alanı ise policy’nin sonucuna göre nasıl davranış sergileyeceğini belirlemektedir. Positive, şart sağlandığı sürece erişime izin verecekken; Negative ise şart sağlandığı taktirde erişimi reddedecektir. Negative‘i blacklist gibi düşünebilirsiniz… -şu role sahip olanlar asla giremesin- mantığında bir davranış için tercih edilebilir…

    User-Based Policy;
    Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Burada da örneğimizi zenginleştirmek için kullanıcının spesifik olarak ‘gncy’ olup olmadığını değerlendiren bir politika oluşturuyoruz.Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Evet… Artık iki adet politikamız hazır olduğuna göre izinleri tanımlamaya geçebiliriz.

  • Adım 6 (Permission Tanımlama)
    Permission, önceki adımlarda oluşturulan resource, scope ve policy üçlüsünün birleştirildiği noktadır. Yani; -hangi kaynak için, hangi işlemde, hangi policy geçerlidir?- sorusunun cevabını vereceğimiz nihai noktadır. Bunun için ‘Authorization’ → ‘Permissions’ sekmesine gelinmesi gerekmektedir.Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Burada dikkat ederseniz karşımıza Resource-Based Permission ve Scope-Based Permission kavramları çıkmaktadır. Şimdi gelin bunlar nedir izah ederek devam edelim…

    • Resource-Based Permission: Sadece resource’a bakan, scope’u önemsemeyen permission türüdür. Mantığı salt olarak -bu kaynağa erişim var mı?- şeklindedir… Aksiyon ayrımı gözetilmemektedir. Basit yetkilendirme süreçlerinde tercih edilir.
    • Scope-Based Permission: Resource ile scope birlikte değerlendirilir. -Bu kaynağın şu işlemini yapabilir mi?- tarzında detaylı yetkilendirme süreçlerinde kullanılır. Örneğin; bir resource’a read yapılabiliyor olsun ancak write ve delete yapılamıyor olsun. İşte böyle aksiyon bazlı kontrol gereken bir senaryoda scope-based permission’dan istifade edilebilir.

    Resource permission kapıyı açarken, scope permission ise içeride ne yapılacağını belirler…

    Şimdi bizler UMA 2.0’a yakışır seviyede scope-based permission oluşturarak devam edelim…Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Burada Document Resource kaynağı üzerinde, read aksiyonu (scope) için Has User Role politikasının uygulanacağı tanımlanmaktadır. Tabi bu tanımda bizler için önem arz eden bazı noktalar mevcuttur; Apply to resource type, bu permission’ın tek bir resource’a mı uygulanacağı, yoksa o tipteki tüm resource’lara mı uygulanacağını ifade eden bir yapılandırmadır. Kapalı olduğu taktirde, oluşturulacak permission sadece seçilen resource için geçerli olacak, açıldığında ise resource type’a göre uygulama sağlanacaktır. Çok sayıda aynı tipten resource’un söz konusu olduğu ve hepsine aynı kuralın uygulanacağı durumlarda kullanılabilir. Decision Strategy ise birden fazla policy’nin olduğu durumlarda kararın nasıl verileceğini yapılandırmaktadır.

  • Adım 7 (Postman Client Oluşturma)
    Keycloak kanadında çoğu yapılandırmalarımızı tamamlamış bulunuyoruz. Sırada bunları test edebilmek için kullanıcının kullanacağı bir client tasarlamamız gerekmektedir. Tabi bunun için bizler oturup client yazmayacak, bu noktada Postman’den istifade edeceğiz. Haliyle Keycloak’da şu konfigürasyonlara sahip bir client oluşturmamız yeterli olacaktır:

    Client ID: postman-client
    Client authentication: OFF ← (public client, secret gerekmez)
    Standard flow: ON
    Direct access grants: ON
    Valid redirect URIs: https://oauth.pstmn.io/v1/callback
  • Adım 8 (Asp.NET Core API Yapılandırma)
    Evet… Artık Asp.NET Core API kısmını yapılandırmaya geçebiliriz. Burada yapılması gereken temel işlem öncelikle konfigürasyonel değerlerin mimariye aşağıdaki gibi taşınmasıdır;

    {
      .
      .
      .,
      "Keycloak": {
        "Authority": "http://localhost:8080/realms/master",
        "ClientId": "uma-resource-server-api",
        "ClientSecret": "b4NkeoNJk35a9zykXGkJDxMSovyaxuIw",
        "TokenEndpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token",
        "PermissionEndpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token"
      }
    }
    
  • Adım 9 (Permission Ticket Alma)
    Keycloak’dan permission ticket alabilmek için UmaTokenService isimli bir sınıf oluşturalım ve içeriğine aşağıdaki çalışmayı ekleyelim;

        public class UmaTokenService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            //Keycloak'a -bu kaynak için izin bileti ver- diyen metottur.
            //Dönen ticket, 401 response içinde client'a gönderilir.
            //Client bu ticket'ı alıp Keycloak'a götürerek RPT edinir.
            public async Task<string?> GetPermissionTicketAsync(string resourceId, string scope)
            {
                var httpClient = httpClientFactory.CreateClient();
    
                //Önce resource server'ın kendi access token'ını alıyoruz.
                var resourceServerToken = await GetResourceServerAccessTokenAsync(httpClient);
    
                httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", resourceServerToken);
    
                //UMA Permission Endpoint'ine istek atarak permission ticket alıyoruz.
                var permissionEndpoint = $"{configuration["Keycloak:Authority"]}/authz/protection/permission";
                var body = new[] {
                    new {
                        resource_id = resourceId,
                        resource_scopes = new[] { scope }
                    }
                };
                var response = await httpClient.PostAsJsonAsync(permissionEndpoint, body);
                if (!response.IsSuccessStatusCode) return null;
    
                var result = await response.Content.ReadFromJsonAsync<PermissionTicketResponse>();
                return result.Ticket;
    
                async Task<string?> GetResourceServerAccessTokenAsync(HttpClient httpClient)
                {
                    var formData = new Dictionary<string, string>
                    {
                        ["grant_type"] = "client_credentials",
                        ["client_id"] = configuration["Keycloak:ClientId"]!,
                        ["client_secret"] = configuration["Keycloak:ClientSecret"]!
                    };
    
                    var response = await httpClient.PostAsync(configuration["Keycloak:TokenEndpoint"]!, new FormUrlEncodedContent(formData));
                    var result = await response.Content.ReadFromJsonAsync<TokenResponse>();
    
                    return result?.AccessToken ?? throw new Exception("Resource server token alınamadı!");
                }
            }
        }
    
  • Adım 10 (Permission Ticket’ı RPT’ye Dönüştürme)
    Şimdi de client’ın elde ettiği permission ticket ile Keycloak’dan RPT edinilmesini sağlayacak çalışmayı ekleyelim;

        public class UmaTokenService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
        {
            .
            .
            .
    
            public async Task<string?> ExchangeForRptAsync(string userAccessToken, string permissionTicket)
            {
                var httpClient = httpClientFactory.CreateClient();
                var formData = new Dictionary<string, string>
                {
                    ["grant_type"] = "urn:ietf:params:oauth:grant-type:uma-ticket",
                    ["client_id"] = configuration["Keycloak:ClientId"]!,
                    ["client_secret"] = configuration["Keycloak:ClientSecret"]!,
                    ["ticket"] = permissionTicket
                };
                httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", userAccessToken);
                var response = await httpClient.PostAsync(configuration["Keycloak:TokenEndpoint"]!, new FormUrlEncodedContent(formData));
    
                if (!response.IsSuccessStatusCode) return null;
    
                var result = await response.Content.ReadFromJsonAsync<TokenResponse>();
                return result?.AccessToken;
            }
        }
    

    Burada urn:ietf:params:oauth:grant-type:uma-ticket değeri, UMA 2.0 akışını başlatan ve görüldüğü üzere standart olanlardan farklı olan özel bir OAuth grant type’ıdır! Bu grant type ile Keycloak, normal bir token değil, değerlendirilmiş bir token vereceğini anlamaktadır!

    Ayrıca burada kullanılan access token’ın kullanıcı token’ı olduğuna dikkatinizi çekerim!

  • Permission Ticket & RPT’yi İncelelim
    Şimdi burada araya girerek Keycloak’dan edineceğimiz permission ticket ve RPT’yi incelemekte fayda görmekteyim… Nesini inceleyeceğiz? diye sorarsanız eğer bunların nasıl yapıya sahip olduklarını görmemiz yeterli olacaktır diye düşünüyorum.

    Tabi, geliştirmemizin bu aşamasında sizlerin permission ticket ile birlikte RPT’yi edinebilmeniz pek mümkün değil. Elbette ki sonraki adımlar kapsamında yapılması gereken bazı hamleler olacaktır. Lakin ben deniz, bu değerleri öncelikli olarak elde edip yine de sizlere göstermek istiyorum.

    Şimdi bakarsanız aşağıda hem permission ticket hem de RPT token’larını göreceksiniz;Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Bunlar yapısal olarak esasında birer JWT’dirler. Evet, haliyle jwt.io‘dan bunların içeriğine göz atabiliriz…

    Permission Ticket RPT
    {
    “exp”: 1776953939,
    “iat”: 1776953879,
    “permissions”: [
    {
    “scopes”: [
    “read”
    ],
    “rsid”: “dbfb5c18-4166-4638-8e84-faff8f5f9126”
    }
    ],
    “jti”: “563a0953-1d31-395f-b88a-8985f8e86ab8-1776953879093”,
    “aud”: “http://localhost:8080/realms/master”,
    “sub”: “37577179-72d9-4fe4-90a6-84debfc17f39”,
    “azp”: “uma-resource-server-api”
    }
    {
    “exp”: 1776953939,
    “iat”: 1776953879,
    “jti”: “ntrtna:9636c515-dd99-1d94-92ce-92349eec5f18”,
    “iss”: “http://localhost:8080/realms/master”,
    “aud”: “uma-resource-server-api”,
    “sub”: “3fe29fb3-8b9e-4714-996c-ca3c5c589935”,
    “typ”: “Bearer”,
    “azp”: “postman-client”,
    “sid”: “iA_VBjs0yeHZ7WgmFkcb3kES”,
    “acr”: “1”,
    “allowed-origins”: [
    “*”
    ],
    “realm_access”: {
    “roles”: [
    “create-realm”,
    “default-roles-master”,
    “offline_access”,
    “admin”,
    “uma_authorization”,
    “user”
    ]
    },
    “resource_access”: {
    “master-realm”: {
    “roles”: [
    “view-identity-providers”,
    “view-realm”,
    “manage-identity-providers”,
    “impersonation”,
    “create-client”,
    “manage-users”,
    “query-realms”,
    “view-authorization”,
    “query-clients”,
    “query-users”,
    “manage-events”,
    “manage-realm”,
    “view-events”,
    “view-users”,
    “view-clients”,
    “manage-authorization”,
    “manage-clients”,
    “query-groups”
    ]
    },
    “account”: {
    “roles”: [
    “manage-account”,
    “manage-account-links”,
    “view-profile”
    ]
    }
    },
    “authorization”: {
    “permissions”: [
    {
    “scopes”: [
    “read”
    ],
    “rsid”: “dbfb5c18-4166-4638-8e84-faff8f5f9126”,
    “rsname”: “Document Resource”
    }
    ]
    },
    “scope”: “profile email”,
    “email_verified”: false,
    “preferred_username”: “admin”
    }
    rsid kaynağı için ‘read’ scope’una izin ticket’ı istenmektedir. Eğer politikalar doğrultusunda şartlar geçerliyse özel yetkilendirilmiş RPT oluşturulmaktadır Dikkat ederseniz authorization.permissions alanında hangi resource’a hangi yetkinin verildiği belirtilmektedir.
  • Adım 11 (RPT Validator Oluşturma)
    Çalışmalara devam edersek eğer RPT içerisinde gelen authorization.permissions alanını okuyarak gerçek izinleri kontrol etmemiz gerekmektedir. Bu işlemi de aşağıdaki gibi bir validator ile gerçekleştireceğiz;

        public class RptValidator
        {
            /// <summary>
            /// RPT'nin payload'ını decode edip içindeki izinlerin istenen kaynağa ve scope'a sahip olup olmadığını kontrol ediyoruz.
            /// 
            /// RPT payload örneği : 
            ///  
            /// {
            ///   "authorization": {
            ///    "permissions": [
            ///      {
            ///        "scopes": [
            ///          "read"
            ///        ],
            ///        "rsid": "dbfb5c18-4166-4638-8e84-faff8f5f9126",
            ///        "rsname": "Document Resource"
            ///      }
            ///    ]
            ///  }
            /// 
            /// 
            /// </summary>
            public bool HasPermission(string rptToken, string resourceName, string scope)
            {
                try
                {
                    var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
                    var jwt = jwtSecurityTokenHandler.ReadJwtToken(rptToken);
    
                    var authorizationClaim = jwt.Claims.FirstOrDefault(c => c.Type == "authorization")?.Value;
    
                    if (authorizationClaim is null) return false;
    
                    var authData = JsonSerializer.Deserialize<AuthorizationClaim>(authorizationClaim);
    
                    return authData?.Permissions?.Any(p => p.ResourceName == resourceName && (p.Scopes?.Contains(scope) ?? false)) ?? false;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }
    
    
        public record AuthorizationClaim(
            [property: JsonPropertyName("permissions")] List<PermissionClaim>? Permissions
        );
    
        public record PermissionClaim(
            [property: JsonPropertyName("rsid")] string? ResourceId,
            [property: JsonPropertyName("rsname")] string? ResourceName,
            [property: JsonPropertyName("scopes")] List<string>? Scopes
        );
    
  • Adım 12 (UMA Middleware Geliştirme)
    Şimdi de her isteği yakalayıp bazı durumları kontrol edecek çalışmalar gerçekleştirmemiz gerekmektedir… Şöyle ki, gelen istekteki token; normal access token ise permission ticket üretecek ve 401 döndüreceğiz, yok eğer RPT ise içindeki permission’ları doğrulayacak ve duruma göre erişime izin vereceğiz. İşte bunun için aşağıdaki gibi bir middleware oluşturmamız tam yerinde olacaktır;

        public class UmaMiddleware(UmaTokenService umaTokenService, RptValidator rptValidator) : IMiddleware
        {
            public async Task InvokeAsync(HttpContext context, RequestDelegate next)
            {
                var path = context.Request.Path.Value ?? "";
    
                //UMA koruması altındaki path'leri belirliyoruz. Örneğin /documents/{id} path'i için id'yi alıp o kaynağa erişim izni olup olmadığını kontrol edeceğiz.
                if (!path.StartsWith("/api/documents", StringComparison.OrdinalIgnoreCase))
                {
                    await next(context);
                    return;
                }
    
                var authorization = context.Request.Headers.Authorization.ToString();
    
                if (string.IsNullOrEmpty(authorization) || !authorization.StartsWith("Bearer "))
                {
                    //Token yok → UMA akışını başlat
                    await ReturnPermissionTicket(context, "Document Resource", "read");
                    return;
                }
    
                var token = authorization["Bearer ".Length..];
    
                //Token RPT mi kontrol ediyoruz... (authorization code içeriyor mu kontrol ediyoruz)
                var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
                var jwt = jwtSecurityTokenHandler.ReadJwtToken(token);
                if (jwt.Claims.Any(c => c.Type == "authorization"))
                {
                    var requiredScope = GetScopeForMethod(context.Request.Method);
    
                    if (rptValidator.HasPermission(token, "Document Resource", requiredScope))
                        await next(context);
                    else
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                        await context.Response.WriteAsync("Bu kaynak için yetkiniz bulunmamaktadır!");
                    }
                }
                else
                {
                    //Normal access token ise permission ticket üretiyor ve 401 dönüyoruz. Client bu ticket'ı alıp Keycloak'tan RPT edinir ve tekrar istekte bulunur.
                    await ReturnPermissionTicket(context, "Document Resource", GetScopeForMethod(context.Request.Method));
                }
    
                string GetScopeForMethod(string method) => method switch
                {
                    "GET" => "read",
                    "POST" => "write",
                    "PUT" => "write",
                    "DELETE" => "delete",
                    _ => "read"
                };
            }
    
            async Task ReturnPermissionTicket(HttpContext context, string resourceId, string scope)
            {
                var ticket = await umaTokenService.GetPermissionTicketAsync(resourceId, scope);
    
                if (ticket is null)
                {
                    context.Response.StatusCode = 500;
                    return;
                }
    
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    
                //WWW-Authenticate header'ında UMA realm, as_uri ve ticket bilgilerini client'a gönderiyoruz. Client bu bilgileri kullanarak Keycloak'tan RPT alacak.
                //Bu bir UMA standardıdır ve client'ların 401 response aldıklarında ne yapmaları gerektiğini anlamalarını sağlar.
                context.Response.Headers[HeaderNames.WWWAuthenticate] = $""" UMA realm="master", as_uri="http://localhost:8080/realms/master", ticket="{ticket}" """;
    
                await context.Response.WriteAsJsonAsync(new
                {
                    error = "uma_redirect",
                    message = "Permission ticket alındı. Bu ticket ile RPT edinin!",
                    ticket = ticket,
                    token_endpoint = "http://localhost:8080/realms/master/protocol/openid-connect/token"
                });
            }
        }
    

    Burada dikkat ederseniz gelen istekteki token 14. satırda elde edildikten sonra gerekli kontroller sağlanmaktadır. Sizlere ilginç gelebilecek olan 16 ile 21. satır aralığındaki, token’ın olmama durumuna istinaden başlatılan UMA akışıdır. Şimdi burada la madem token yok neden isteği reddetmiyoruz! diye soruyor olabilirsiniz. Ancak hatırlamanızı istiyorum ki UMA’nın felsefesi isteği reddetmeden önce kullanıcıya nasıl erişebileceğini söylemesinden geçmektedir. Yani burada OAuth 2.0 tarzı, token yoksa eğer 401 status code’u ile istek reddedilsin şeklinde bir mantıktan ziyade; UMA tarzı, token olmasa dahi 401 status code’unda permission ticket ile client’a dönüş yapılsın ve bu ticket ile Keycloak’tan RPT alınması sağlansın mantığında bir çalışma söz konusudur. Ha tabi siz yine de token’ın olmadığı bir duruma karşın isterseniz direkt isteği reddedebilirsiniz…

    26 ile 39. satır aralığına göz atarsanız eğer içerisinde ‘authorization’ claim’i sorgulanarak mevcut token’ın RPT olup olmadığı kontrol edilmektedir. Eğer RPT ise gelen isteğin mahiyetine göre (GET, POST vs.) uygun scope belirlenip (GetScopeForMethod), önceki satırlarda oluşturduğumuz Document Resource‘a dair böyle bir aksiyonun olup olmadığı RptValidator aracılığıyla değerlendirilmektedir. Yok eğer ilgili token, normal bir access token ise 43. satırda ReturnPermissionTicket metodu aracılığıyla ilgili resource’a uygun scope için permission ticket talep edilmektedir.

    Tüm bunların dışında 56 ile 79. satır aralığında permission ticket oluşturulmasını sağlayan ReturnPermissionTicket metodunun içindeki 70. satırda bulunan karışık ifadeyi de izah etmemiz gerekirse eğer bu, HTTP standardındaki WWW-Authenticate header’ının UMA versiyonudur. Bu header ile client’a, -bu kaynağa erişebilmek için şu şekilde authenticate olmalısın- denilmektedir. Yani bir başka deyişle client’a, -git Keycloak’tan UMA ile yetki al- demenin resmi HTTP cevabı bu şekildedir diyebiliriz…

  • Adım 13 (/api/documents/{id} Endpoint’ini Geliştirme)
    Önceki adımlarda oluşturduğumuz resource’un URIs’inde belirtilen path’e karşılık bir endpoint oluşturalım;

    app.MapGet("/api/documents/{id}", (string id) =>
    {
        //Buraya gelindiyse middleware RPT'yi doğrulamış demektir...
        return Results.Ok(new
        {
            id,
            title = "Document",
            content = "Bu içerik UMA 2.0 ile korunmaktadır.",
            accessedAt = DateTime.UtcNow
        });
    });
    
  • Adım 14 (Authenticate’i Yapılandırma ve Temel Ayarlar)
    Şimdi de temel JWT authentication yapılanmasıyla birlikte geri kalan gerekli IoC container konfigürasyonlarını Program.cs dosyasında tamamlayalım;

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddHttpClient();
    builder.Services.AddSingleton<UmaTokenService>();
    builder.Services.AddSingleton<RptValidator>();
    builder.Services.AddSingleton<UmaMiddleware>();
    
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
        {
            options.Authority = builder.Configuration["Keycloak:Authority"];
            options.Audience = builder.Configuration["Keycloak:ClientId"];
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new()
            {
                ValidateIssuer = true,
                ValidateAudience = false, //UMA'da audience kontrolü middleware'da yapılmaktadır. Burada sadece token'ın Keycloak tarafından imzalanıp imzalanmadığı kontrol edilmektedir.
                ValidateLifetime = true
            };
        });
    
    var app = builder.Build();
    app.UseAuthentication();
    
    app.UseMiddleware<UmaMiddleware>();
    
    .
    .
    .
    
    app.Run();
    
  • Adım 15 (Test)
    Evet… Her şey hazır… Artık testimize geçebiliriz. Bunun için Postman üzerinden kullanıcı adına aşağıdaki istek aracılığıyla access token talebinde bulunalım:

    🔗 POST http://localhost:8080/realms/master/protocol/openid-connect/token
    grant_type: password
    client_id: postman-client
    username: gncy
    password: 123
    scope: openid

    Ardından bu access token ile Asp.NET Core API uygulamamızın, resource’a uygun şekilde tasarladığımız endpoint’ine istekte bulunalım:

    Authorization: Bearer ACCESS_TOKEN
    🔗 GET https://localhost:7086/api/documents/123

    Bu istek sürecinde middleware devreye girecek ve ilgili access token’ın RPT olmadığını anlayarak, Keycloak’tan ilgili kaynak için bir permission ticket isteyecektir. Keycloak, ticket üretecek ve Asp.NET Core’da bu ticket’ı 401 status code’u eşliğinde client’a aşağıdaki gibi geri döndürmüş olacaktır.Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Şimdi bu ticket’ı alıp, yine Postman aracılığıyla (çünkü hala client’ımız o) aşağıdaki adrese yeni bir access token talebinde bulunalım:

    Authorization: Bearer ACCESS_TOKEN
    🔗 POST http://localhost:8080/realms/master/protocol/openid-connect/token
    grant_type: urn:ietf:params:oauth:grant-type:uma-ticket
    ticket: PERMISSION_TICKET

    Eğer ki bu istek neticesinde aşağıdaki access_denied hatasını alıyorsanız, bilin ki politikaya uymayan bir durum söz konusudur.Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Ne olabilir? sorusuna karşın ilk kontrol edeceğiniz yer ilgili resource’un politikaları olmalıdır. Hatırlarsanız eğer bizler ‘Document Read Permission’ adını verdiğimiz permission’da ‘Has User Role’ politikasını uygulamıştık. Haliyle bu politika gereği ilgili kullanıcının ‘user’ rolüne sahip olması gerekmektedir. Eğer bu hatayı alıyorsanız muhtemelen test işleminde kullandığınız user’da ilgili rolün atanmadığını söyleyebiliriz.

    Eğer her şey yolundaysa, permission ticket ile talep edilen özel yetkilendirilmiş access token (RPT) aşağıdaki gibi elde edilecektir:Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12Bu aşamadan sonra tek yapılması gereken RPT access token’ı aracılığıyla tekrar Asp.NET Core uygulamasındaki endpoint’e istekte bulunmaktır!Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12İşte bu kadar… 🙂

UMA’yı Custom Authorization Requirement + Handler Yoluyla Uygulama (En Doğru Yol)

Yukarıdaki çalışmada UMA’yı uygularken permission’daki scope kontrolünü de manuel bir şekilde sağlamaktayız. Halbuki ideal olan bunu Asp.NET Core’un native yetkilendirme pipeline’ına yansıtabilmek ve orada bir policy mantığında gerçekleştirebilmektir.

Bunun için aşağıdaki gibi custom bir authorization requirement oluşturarak süreci daha efektif hale getirebiliriz:

    public record UmaPermissionRequirement(string ResourceName, string Scope) : IAuthorizationRequirement;

Devamında ise bu requirement’ı aşağıdaki handler ile doğrulayabilir ve RPT kontrolünü sağlayabiliriz:

    public class UmaPermissionHandler(RptValidator rptValidator) : AuthorizationHandler<UmaPermissionRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UmaPermissionRequirement requirement)
        {
            if (context.Resource is not HttpContext httpContext)
            {
                context.Fail();
                return Task.CompletedTask;
            }

            var authorizationHeader = httpContext.Request.Headers.Authorization.ToString();

            //Hiç token yoksa başarısız authorization'ın detaylarıyla birlikte Fail'liyoruz
            if (string.IsNullOrEmpty(authorizationHeader) || !authorizationHeader.StartsWith("Bearer "))
            {
                context.Fail(new AuthorizationFailureReason(this, "no_token"));
                return Task.CompletedTask;
            }

            var token = httpContext.Request.Headers.Authorization.ToString().Replace("Bearer ", "");

            //Eğer token varsa RPT'mi değil mi kontrol ediyoruz
            var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
            var jwt = jwtSecurityTokenHandler.ReadJwtToken(token);
            if (!jwt.Claims.Any(c => c.Type == "authorization"))
            {
                context.Fail(new AuthorizationFailureReason(this, "not_rpt"));
                return Task.CompletedTask;
            }

            //RPT ise permission'ları kontrol ediyoruz.
            if (rptValidator.HasPermission(token, requirement.ResourceName, requirement.Scope))
                context.Succeed(requirement);
            else
                context.Fail();

            return Task.CompletedTask;
        }
    }

Devamında ise authorization’ın yalnızca başarısız olduğu durumlarda işlem yapacak olan aşağıdaki gibi bir middleware ile de gerekli kontrolleri sağlayabilir ve fail sebebi no_token yahut not_rpt ise permission ticket alabilir yahut permission_denied ise de 403 döndürebiliriz:

    public class UmaAuthorizationMiddlewareResultHandler(UmaTokenService umaTokenService) : IAuthorizationMiddlewareResultHandler
    {
        private readonly AuthorizationMiddlewareResultHandler _default = new();
        public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
        {
            //Authorization başarılıysa direkt geçiriyoruz
            if (authorizeResult.Succeeded)
            {
                await next(context);
                return;
            }

            //Hatalıysa nedenine bakıyoruz
            var reason = authorizeResult.AuthorizationFailure?.FailureReasons.FirstOrDefault()?.Message;

            //RPT varsa ama yetersiz permission'sa → 403
            if (reason == "permission_denied")
            {
                context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                await context.Response.WriteAsJsonAsync(new
                {
                    error = "forbidden",
                    message = "Bu resource için yetkiniz bulunmamaktadır."
                });
                return;
            }

            //Token yoksa ya da RPT değilse → 401
            if (reason is "no_token" or "not_rpt")
            {
                //Hangi endpoint için ticket isteniyorsa bunu requirement'tan almak için policy'e bakıyoruz
                var requirement = policy.Requirements.OfType<UmaPermissionRequirement>().FirstOrDefault();

                if (requirement == null)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                    return;
                }

                var ticket = await umaTokenService.GetPermissionTicketAsync(requirement.ResourceName, requirement.Scope);

                if (ticket == null)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    return;
                }

                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                context.Response.Headers[HeaderNames.WWWAuthenticate] = $""" UMA realm="master", as_uri="http://localhost:8080/realms/master", ticket="{ticket}" """;

                await context.Response.WriteAsJsonAsync(new
                {
                    error = "uma_redirect",
                    message = "Permission ticket alındı. Bu ticket ile RPT edinin!",
                    ticket = ticket,
                    token_endpoint = "http://localhost:8080/realms/master/protocol/openid-connect/token"
                });
            }

            //Token süresi dolması vs. gibi diğer durumlar için default davranışı devreye alıyoruz
            await _default.HandleAsync(next, context, policy, authorizeResult);
        }
    }

Tüm bu işlemlerden sonra tek yapılması gereken aşağıdaki gibi pipeline’a uygun bir şekilde scope’lar üzerinden politikaların oluşturulması ve endpoint’te kullanılmasıdır…

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("document:read", policy =>
        policy.Requirements.Add(new UmaPermissionRequirement("Document Resource", "read")));

    options.AddPolicy("document:write", policy =>
        policy.Requirements.Add(new UmaPermissionRequirement("Document Resource", "write")));
});
app.MapGet("/api/documents/{id}", (string id) =>
{
    //Buraya gelindiyse middleware RPT'yi doğrulamış demektir...
    return Results.Ok(new
    {
        id,
        title = "Document",
        content = "Bu içerik UMA 2.0 ile korunmaktadır.",
        accessedAt = DateTime.UtcNow
    });
}).RequireAuthorization(policyNames: "document:read");

İşte bu kadar basit…

Keycloak'da Advanced Authorization Services (UMA 2.0) Sistemini İnceleyelim... #12

UMA 2.0 akışının ve RPT’nin davranışsal olarak görsel özeti…

Tabi burada, uygulama zemininde dikkat etmenizde fayda göreceğim bazı tecrübevi noktalar mevcuttur. Bunları sizler yaşadıkça anlayacak ve sindirecek olsanız da yine hafiften önden vurgulamakta fayda görüyorum…

UMA 2.0 senaryolarında, RPT alım sürecinde access_denied hatası yüksek olasıdır. Burada ilk bakacağınız nokta her daim permission’ın policy’lerinin doğrulanıp doğrulanmadığı olmalıdır. Ayrıca 401 döngüsü de bazen kaçınılmaz olabilmektedir. Burada da kontrol edilen access token içerisinde ‘authorization’ claim’i var mı yok mu tarafınızca manuel kontrol edilmelidir. Ha ayrıca client’ın authorization ayarlarında ‘Remote resource management’ yapılandırmasını aktifleştirme de unutulmamalıdır. Bunlara dikkat edildiği sürece diğer yaşanacak hatalar artık yüksek ihtimal UMA’nın dışında başka nedenlere dayanmaktadır 🙂

Evet, görüldüğü üzere UMA 2.0 gibi kompleks ve hassas yapılandırma gerektiren bir akışı a’dan z’ye tam teferruatlı teorik bir inceleme eşliğinde, pratikte de ele almış ve siz değerli okuyucularımızla birlikte deneyimlemiş bulunuyoruz.

Nihai olarak;
İçerik boyunca, klasik rol bazlı yetkilendirmenin ötesine geçerek UMA 2.0 standardını temel alan Keycloak Advanced Authorization Services’i teorik ve pratik boyutlarıyla ele almış bulunuyoruz. Resource, scope, policy ve permission kavramlarının bir araya geldiği ve dinamik izin akışları ile birlikte hassas erişim kontrolleri gerektiren senaryolarda sunduğu esneklikle öne çıkan bu akışı, Asp.NET Core entegrasyonu üzerinden adım adım, permission ticket ve RPT mekanizmalarının pratikte nasıl işlediğini deneyimleyerek gözler önüne sermiş bulunuyoruz.

Unutulmamalıdır ki, UMA 2.0 her senaryo için uygun değildir! Basit yetkilendirme ihtiyaçlarında gereksiz karmaşıklığa yol açabileceği gibi, doğru kullanıldığında ise merkezi ve dinamik bir yetki yönetimi imkanı sunmaktadır.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Örnek çalışmaya aşağıdaki GitHub reposundan erişebilirsiniz.
https://github.com/gncyyldz/Keycloak.Examples

Bu repository, ilgili konuya dair örnek çalışmanın kaynak kodlarını ve mimari yapısını içermektedir. Detaylar için GitHub üzerinden incelemede bulunabilirsiniz.


GitHub’da Görüntüle →

Bunlar da hoşunuza gidebilir...

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir