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

Asp.NET Core İçin gRPC’de Authentication ve Authorization İşlemleri

Merhaba,

gRPC, her ne kadar uzak sunuculardaki metotları sanki kendi ortamının birer parçasıymış gibi çağırabilen sistem olsa da kimlik doğrulama(authentication) ve yetkilendirme(authorization) nitelikleri sayesinde bir kullanıcıyla ilişkilendirilebilmekte ve böylece daha korunaklı ve tanımlı veri akışı sağlayabilmektedir. Özellikle günümüzde oldukça popüler olan JWT’ye dayalı yapılandırılmış bir kimlik doğrulama sayesinde süreç daha dinamik hale getirilebilmektedir.

gRPC, gelen istekler üzerinde kimlik doğrulamayı gerçekleştirebilmek için temel Asp.NET Core mimarisinin authentication ve authorization özelliklerini kullanarak gerekli yapılanmayı sağlamaktadır. Bunun için yapılması gereken ilk iş, gRPC Server uygulamasının ‘Startup.cs’ dosyasında UseAuthentication ve UseAuthorization middleware’lerini sırasıyla çağırmaktır.

    public class Startup
    {
        .
        .
        .

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            .
            .
            .
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<MessageService>();
            });
            .
            .
            .
        }
    }

Ardından yapılması gereken ikinci işlem ise kullanılacak kimlik doğrulama mekanizmasının yapılandırmasını oluşturmaktır. Haliyle bizler burada ilk paragrafta verdiğimiz ipucundan da anlaşılacağı üzere JWT konfigürasyonunu örneklendireceğiz.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();

            services.AddAuthorization();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new()
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sebepsiz bos yere ayrilacaksan...")),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    NameClaimType = "name"
                };
            });
        }
        .
        .
        .
    }

Misal olarak yukarıdaki gibi bir JWT konfigürasyonunu gerçekleştirdikten sonra base servislerimizi aşağıdaki gibi Authorize attribute’u ile işaretleyebiliriz.

    [Authorize]
    public class MessageService : MessageBase
    {
        public override async Task GetMessage(MessageRequest request, IServerStreamWriter<MessageResponse> responseStream, ServerCallContext context)
        {
            .
            .
            .
        }
    }

Artık bu vaziyette, client tarafından gelecek olan ve doğrulanabilir JWT barındırmayan tüm istekler gRPC Server tarafından aşağıdaki hatayla reddedilecektir.
Asp.NET Core İçin gRPC'de Authentication ve Authorization İşlemleri
Haliyle bu durumda artık client, ya gRPC Server’dan ya da farklı bir sunucudan(auth server misali) JWT talebinde bulunmalıdır. Bunun için aşağıdaki gibi bir JWT handler sınıfı tasarlayalım ve örnek olarak gRPC Server uygulaması üzerinden bir API açarak client’ın talebi neticesinde JWT’yi üretip gönderelim.
‘TokenHandler.cs’ :

    public static class TokenHandler
    {
        public static string CreateAccessToken()
        {
            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sebepsiz bos yere ayrilacaksan..."));
            SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            JwtSecurityToken securityToken = new JwtSecurityToken(
                notBefore: DateTime.Now,
                signingCredentials: signingCredentials,
                claims: new List<Claim> { new("name", "Gençay") },
                expires: DateTime.Now.AddDays(1)
                );
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            string accessToken = tokenHandler.WriteToken(securityToken);
            return accessToken;
        }
    }

‘AuthController.cs’ :

    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        public IActionResult GetToken()
            => Ok(TokenHandler.CreateAccessToken());
    }

‘Startup.cs’ :

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            .
            .
            .
            services.AddGrpc();
            services.AddControllers();
            .
            .
            .
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            .
            .
            .
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<MessageService>();
                endpoints.MapControllers();
            });
            .
            .
            .
        }
    }

Yukarıdaki çalışmadan da anlaşılacağı üzere client, https://localhost:5001/api/auth adresinden JWT talebinde bulunabilecek ve elde ettiği JWT ile aşağıdaki gibi istek gönderebilecektir.

    class Program
    {
        static async Task Main(string[] args)
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var messageClient = new Message.MessageClient(channel);

            var headers = new Metadata();
            headers.Add("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiR2Vuw6dheSIsIm5iZiI6MTYzNTAzMTQzMiwiZXhwIjoxNjM1MTE3ODMyfQ.gIiKIVZugwIcOrW-TQPls6vCvL5klhVYXQllKf1zeJw");

            var response = messageClient.GetMessage(new MessageRequest() { Message = "Messajları gönder..." }, headers: headers);
            var cancellationToken = new CancellationToken();
            while (await response.ResponseStream.MoveNext(cancellationToken))
                Console.WriteLine(response.ResponseStream.Current.Message);
        }
    }

Yukarıdaki kod bloğunda 8 ile 11. satır aralığını incelerseniz eğer bir ‘Metadata’ türünden nesne üretilmekte ve bu nesneye ‘Add’ fonksiyonuyla ilgili JWT verilmektedir. Ardından 11. satırda ilgili client nesnesi üzerinden server’da ki fonksiyonu(GetMessage) tetiklerken ‘headers’ parametresine ilgili nesne verilerek kullanıcı doğrulaması yapılmak istenmektedir.

Kimlik doğrulaması inşa edildikten sonra gRPC Server’da kullanıcı bilgilerine erişebilmek için ‘ServerCallContext’ türünden olan ‘context’ parametresi aşağıdaki gibi kullanılabilir.

    [Authorize]
    public class MessageService : MessageBase
    {
        public override async Task GetMessage(MessageRequest request, IServerStreamWriter<MessageResponse> responseStream, ServerCallContext context)
        {
            .
            .
            .
            var user = context.GetHttpContext().User;
            .
            .
            .
        }
    }
gRPC Client Factory İle Token Kullanımı

gRPC Client Factory ile kimlik doğrulamayı gerçekleştirmek istiyorsanız eğer bu işlem aşağıdaki gibi basitçe halledilebilmektedir.

        public void ConfigureServices(IServiceCollection services)
        {
            string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiR2Vuw6dheSIsIm5iZiI6MTYzNTAzMTQzMiwiZXhwIjoxNjM1MTE3ODMyfQ.gIiKIVZugwIcOrW-TQPls6vCvL5klhVYXQllKf1zeJw";
            services.AddGrpcClient<MessageClient>(options =>
            {
                options.Address = new Uri("https://localhost:5001");
            }).ConfigureChannel(options =>
            {
                var credentials = CallCredentials.FromInterceptor((context, metadata) =>
                {
                    if (!string.IsNullOrEmpty(token))
                        metadata.Add("Authorization", $"Bearer {token}");
                    return Task.CompletedTask;
                });

                options.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
            });
            services.AddControllers();
        }
gRPC Claim Tabanlı Yetkilendirme

gRPC çalışmalarında tıpkı Asp.NET Core uygulamalarında olduğu gibi claim tabanlı yetkilendirmeleri kolaylıkla gerçekleştirebilmekteyiz. Bunun için aşağıdaki gibi bir politika tanımlanması yeterlidir.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();
            .
            .
            .
            services.AddAuthorization(options =>
            {
                options.AddPolicy("RolePolicy", policy => policy.RequireClaim(ClaimTypes.Role, "admin"));
            });
        }
        .
        .
        .
    }

Bu tanımdan sonra artık token üreticisi, generate edilecek JWT değerine içerisinde role değeri barındıran bir claim eklemelidir. Farazi olarak aşağıdaki 12. satır buna bir örnektir.

    public static class TokenHandler
    {
        public static string CreateAccessToken()
        {
            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("sebepsiz bos yere ayrilacaksan..."));
            SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            JwtSecurityToken securityToken = new JwtSecurityToken(
                notBefore: DateTime.Now,
                signingCredentials: signingCredentials,
                claims: new List<Claim> {
                    new("name", "Gençay") ,
                    new(ClaimTypes.Role,"admin")
                },
                expires: DateTime.Now.AddDays(1)
                );
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            string accessToken = tokenHandler.WriteToken(securityToken);
            return accessToken;
        }
    }

Ve son olarak Authorize attribute’u ile işaretlenen ilgili gRPC servisinde aşağıdaki gibi oluşturulan politika belirtilmelidir.

    [Authorize(Policy = "RolePolicy")]
    public class MessageService : MessageBase
    {
        public override async Task GetMessage(MessageRequest request, IServerStreamWriter<MessageResponse> responseStream, ServerCallContext context)
        {
            .
            .
            .
        }
    }

Tabi burada yetkilendirmeyi metot bazlı olacak şekilde de yapabileceğinizi bildiğinizi varsayıyorum.

gRPC Politika Tabanlı Yetkilendirme

Ayrıca claim bazlı yetkilendirmenin dışında farklı politikalar eşliğinde de yetkilendirme usulleri oluşturabilirsiniz. Buna bildiğiniz klasik politika bazlı yetkilendirme denmektedir.

Misal olarak hafta sonu hizmet vermeyecek olan bir gRPC servisi için aşağıdaki gibi bir politika oluşturabiliriz.

    public class DateRequirement : IAuthorizationRequirement
    {
    }
    public class DateHandler : AuthorizationHandler<DateRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DateRequirement requirement)
        {
            if (DateTime.Now.DayOfWeek != DayOfWeek.Saturday && DateTime.Now.DayOfWeek != DayOfWeek.Sunday)
                context.Succeed(requirement);
            else
                context.Fail();
            return Task.CompletedTask;
        }
    }
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddGrpc();
            .
            .
            .
            services.AddAuthorization(options =>
            {
                options.AddPolicy("RolePolicy", policy => policy.RequireClaim(ClaimTypes.Role, "admin"));
                options.AddPolicy("DatePolicy", policy => policy.Requirements.Add(new DateRequirement()));
            });
            services.AddSingleton<IAuthorizationHandler, DateHandler>();
        }
        .
        .
        .
    }

Ardından kullanımı yine aşağıdaki gibi olacaktır.

    [Authorize(Policy = "DatePolicy")]
    public class MessageService : MessageBase
    {
        public override async Task GetMessage(MessageRequest request, IServerStreamWriter<MessageResponse> responseStream, ServerCallContext context)
        {
            .
            .
            .
        }
    }

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

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

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