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

IdentityServer4 Yazı Serisi #4 – Claim Bazlı Yetkilendirme

Merhaba,

IdentityServer4 Yazı Serisinin üçüncü makalesi olan Client Credentials başlıklı makalede IdentityServer4’ün pratiksel temelleri eşliğinde işlevsel mekanizmasına değinmiş ve bir yandan da sadece client’ları yetkilendirme merkezli(Client Credentials) temel bir uygulama geliştirmiştik. Bu içeriğimizde ise yetkilendirmeyi bir adım daha ilerletecek ve claim merkezli yetkilendirme gerçekleştireceğiz. Tabi konumuz bir bütün olan yazı serisinin küçük bir parçası olacağından dolayı bir önceki makalede geliştirdiğimiz uygulama üzerinden devam niteliğinde anlatım sergileyeceğiz.

Claim Nedir?

Öncelikle claim bazlı yetkilendirmenin ne olduğuna değinerek başlayalım. Bunun için claim’i tanımlamamız gerekmektedir. Asp.NET Core Identity – Yazı Dizisini okuyanlar bilir ki, Claim Bazlı Kimlik Doğrulama başlıklı makalemizde claim’in ne olduğunu detaylarıyla açıklamış bulunmaktayız. Hatta ilgili makaleden konuya dair alıntı yaparsak eğer;

kullanıcı hakkında key – value şeklinde hususi bilgiler tutan ve bunları bizlere yaptığımız talepler neticesinde getiren …

şeklinde tanımlama yapılmaktadır.

İşte buradaki claim’i IdentityServer4 için kullanıyorsak eğer altı çizili olan kullanıcı yerine client(istemci) kelimesini koymamız yeterli olacak ve şöyle bir anlam karşımıza çıkacaktır.

Claim; IdentityServer4 mimarisinda client hakkında key – value şeklinde hususi bilgiler tutan bir yapılanmadır. Yapılan taleplerde, client’tan gelen claim’ler Auth Server tarafından elde edilmekte ve işlevsel açıdan değerlendirilmektedir. Buradaki işlev genellikle client’ı yetkilendirmek, yetki alanlarını belirlemek üzere şekillenecektir.

IdentityServer4’te Claim Nasıl Tanımlanır?

Bu sorunun cevabını esasında bir önceki makalemizde(bknz: Client Credentials) vermiş bulunmaktayız. İlgili makaleye göz atarsanız eğer Auth Server uygulaması için yapılan konfigürasyon(Config.cs) tanımlamasında client’lara yerleştirilen ‘AllowedScopes’ alanı eşliğindeki değerler, oluşturulacak JWT’ye ‘scopes’ key’i karşılığında payload olarak eklenmektedirler. Böylece client’a ait token değeri zaten, içerisinde claim değerleri eklenmiş vaziyette elde edilmektedir.

Bu içeriğimizde, client’ı bu scope değerlerine(claim) göre yetkilendirmeyi amaç edinmekteyiz. Bunun için direkt olarak API’lar da politikalar oluşturmamız yeterli ve yerinde olacaktır.

API(lar)’da Claim Bazlı Politika(lar) Oluşturma?

API uygulamasının ‘Startup.cs’ dosyasındaki ‘ConfigureServices’ metodunda aşağıdaki gibi politikalar belirleyebilirsiniz.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                    {
                        options.Authority = "https://localhost:1000";
                        options.Audience = "Garanti";
                    });

            services.AddAuthorization(_ =>
            {
                _.AddPolicy("ReadGaranti", policy => policy.RequireClaim("scope", "Garanti.Read"));
                _.AddPolicy("WriteGaranti", policy => policy.RequireClaim("scope", "Garanti.Write"));
                _.AddPolicy("ReadWriteGaranti", policy => policy.RequireClaim("scope", "Garanti.Write","Garanti.Read"));
                _.AddPolicy("AllGaranti", policy => policy.RequireClaim("scope", "Garanti.Admin"));
                _.AddPolicy("ReadHalkBank", policy => policy.RequireClaim("scope", "HalkBank.Read"));
                _.AddPolicy("WriteHalkBank", policy => policy.RequireClaim("scope", "HalkBank.Write"));
                _.AddPolicy("ReadWriteHalkBank", policy => policy.RequireClaim("scope", "HalkBank.Write", "HalkBank.Read"));
                _.AddPolicy("AllHalkBank", policy => policy.RequireClaim("scope", "HalkBank.Admin"));
            });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            .
            .
            .
            app.UseAuthentication();
            app.UseAuthorization();
            .
            .
            .
        }
    }

Artık uygulamada belli başlı işlemleri tarif eden politikalar mevcuttur. Yukarıdaki politikalara göz atarsanız eğer gelen istekteki token değeri içerisinde ‘scope’ alanındaki değerin karşılığını farklı değerler ile şart koşan birden fazla politika inşa edilmiştir. Eğer ki bir politika ‘RequireClaim’ ile zorunluluğunu ifade ediyorsa burada claim bazlı bir politika söz konusudur. Tabi ki de burada çok daha farklı türlerde politikalarda inşa edilebilir. Lakin bizler konuyu fazla dağıtmamak ve içerik olarak IdentityServer4’te ki authorization mantığını en sade haliyle aktarabilmek için bu ufak politikalar eşliğinde yolumuza devam edelim.

Action’ları Politikalarla Yetkilendirme

Şimdi hangi action’ın hangi politikayı benimseyeceğini bildirmemiz gerekmektedir. Bunun için aşağıdaki gibi ‘Authorize’ attribute’u ile çalışılması yeterlidir;

    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class GarantiBankController : ControllerBase
    {
        [HttpGet("{musteriId}")]
        [Authorize(Policy = "ReadGaranti")]
        public double Bakiye(int musteriId)
        {
            //....
            return 1000;
        }
        [HttpGet("{musteriId}/{tutar}")]
        [Authorize(Policy = "AllGaranti")]
        public double YatirimYap(int musteriId, double tutar)
        {
            return tutar * 0.5;
        }
        .
        .
        .
    }

Burada ‘Authorize’ attribute’u, ilgili action’a erişim yetkisini sınırlandırırken biryandan da ‘Policy’ property’sine bildirilen politika ismi ile ilgili politikanın şartlarının doğrulanmasını da zorunlu hale getirmektedir. Evet, ilgili action’lar belirtilen politikalardaki şartı sağlayan client’lar tarafından tetiklenecektir. Böylece sadece token’ın var olması yeterli olmayacak bir yandan da taşıdığı claim değerleri içerisinde buradaki ‘scope’ gerekliliğini sağlayacak olan değeri de taşıyor olması gerekecektir.

Test

Auth Server dahil tüm API’ları ayağa kaldırarak, client olarak Postman eşliğinde aşağıdaki gibi bir test gerçekleştirelim;

Client JWT olmaksızın istek gönderdiğinde IdentityServer4 Yazı Serisi #4 - Cleam Bazlı Yetkilendirme

IdentityServer4 Yazı Serisi #4 - Cleam Bazlı Yetkilendirme

‘Garanti.Read’ ve ‘Garanti.Write’ yetkilerine(scope) sahip olan ‘GarantiBankasi’ client’ına ait bir JWT elde etme IdentityServer4 Yazı Serisi #4 - Cleam Bazlı Yetkilendirme
JWT ile istek gönderme IdentityServer4 Yazı Serisi #4 - Cleam Bazlı Yetkilendirme
Görüldüğü üzere sadece ‘Garanti.Read’ yetkisini gerektiren ‘ReadGaranti’ politikasını uygulayan ‘Bakiye’ action’ı token ile yapılan talebi doğruladı ve çalıştı.
IdentityServer4 Yazı Serisi #4 - Cleam Bazlı Yetkilendirme
Halbuki ‘Garanti.Admin’ yetkisini gerektiren ‘AllGaranti’ isimli politikayı uygulayan ‘YatirimYap’ action’ı, request içerisindeki token’da ilgili yetkiye karşılık gelen bir scope olmadığı için isteği onaylamadı ve 403 hata kodu döndürdü. 403 hata kodu 401’e nazaran; token’ın var lakin yetkin/scope yok/yetersiz anlamına gelmektedir.

Netice olarak claim bazlı yetkilendirme, claim bazlı politika tasarlamamızı ve ilgili controller ya da action’ların bu politikalar ile davranışlarını şekillendirmemizi gerektiren ve böylece token’dan ziyade, token’ı var olan client’lar arasında yetki ayrımını gerçekleştirmemizi sağlayan bir yapılanmadır. Tasarımsal açıdan sizlerin de taktir edeceği gibi kurumsal yapılanmalarda ihtiyaçlara binaen oldukça efektif çözümler getirmemizi sağlayan olmazsa olmaz tekniklerimizden birisidir diyebiliriz…

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

Not : Örnek uygulamayı indirmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

5 Cevaplar

  1. Nezir Volkan Solak dedi ki:

    Selamlar, yukarıdaki makalede dediğiniz gibi ilgili kısımları uyguladım ve başarılı bir şekilde cevap alabildim. Ancak uygulamaları localhost yerine ip adresle yayınladığımda aşağıdaki hatayı alıyorum. Nedeni ne olabilir?

    System.InvalidOperationException: IDX20803: Unable to obtain configuration from: ‘System.String’.
    —> System.IO.IOException: IDX20804: Unable to retrieve document from: ‘System.String’.
    —> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
    —> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch
    at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
    at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions)
    at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
    at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
    — End of inner exception stack trace —
    at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
    at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
    at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
    at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
    — End of inner exception stack trace —
    at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
    at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
    at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
    — End of inner exception stack trace —
    at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
    at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
    at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
    at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
    at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
    at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
    at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
    at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

    HEADERS
    =======
    Accept: */*
    Connection: keep-alive
    Host: 192.168.1.101:2000
    User-Agent: PostmanRuntime/7.29.2
    Accept-Encoding: gzip, deflate, br
    Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjE0NTEwOEI5NjlEODRCMzE0OUQ2RjkxNEIyRTY3QUQxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjcwNDEzMDMsImV4cCI6MTY2NzA0NDkwMywiaXNzIjoiaHR0cHM6Ly8xOTIuMTY4LjEuMTAxOjEwMDAiLCJhdWQiOiJHYXJhbnRpIiwiY2xpZW50X2lkIjoiR2FyYW50aUJhbmthc2kiLCJqdGkiOiI2NUVGRDQzRTY0QzkxQjU2MjYyNDEwRkMyQzQ5QjYxNyIsImlhdCI6MTY2NzA0MTMwMywic2NvcGUiOlsiR2FyYW50aS5SZWFkIiwiR2FyYW50aS5Xcml0ZSJdfQ.ALeh0TneCAyCHPoZV5nXd7_vkK7M6s2aZ1BoS_g6R4qER-Jcq2lQ6HZtxx4_PKw_VyWx7vBbZqQbQmEF_OnYaumPyWN0Uz1ridzcIClxyyJ19U2jLsiPzAhD-mJUfEafrE4SIsHXPH31IXIDu_X7OVxklioomb0QEEEsy1ePXv0QhRo4ScGY2mGc_rqS_javkfRMb2psFNNN-hYwvK6naoOE3mNBivlqM_wCqdl5-TYwkzyKCEcNlOGY-Pz_E77YLRoBj564y-l0Wqh51N10-2pQI0Ybosw936Vmjn7cFJOklYcx38a35E9XC1KCHwHROcy8axH0ukUUOBxppO9zeQ
    Postman-Token: b746287f-b5f0-4bd4-be95-3639445fd427

  2. indirim kodu dedi ki:

    Gençay Hocam başıkta ve adres içerisinde typo var. claim yazacağına cleam yazıyor. Sitenizin alacağı trafiği etkilemesin isterim. Ellerinize sağlık, iyi ki varsınız.

  3. Mustafa dedi ki:

    Video eğitimleriniz çok güzel ama buradan fazla istifade edemiyorum. Örnek kodlarınız çalıştıramıyorum.
    127.0.0.1:1001 hatası alıyorum. Server ı ayaga kaldramıyorum sanırım. Çözüm için ne yapacam bilmiyorum.

  1. 21 Ekim 2020

    […] IdentityServer4 Yazı Serisi #4 – Cleam Bazlı Yetkilendirme […]

Nezir Volkan Solak için bir yanıt yazın Yanıtı iptal et

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