gRPC - JWT Token 을 통한 인증 처리
2022. 5. 24. 00:00ㆍASPNET/Grpc
반응형
ASP.NET Core 인증과 함께 gRPC를 사용하여 각 호출과 사용자를 연결할 수 있습니다.
Share project 생성 (c# library)
CommonConfiguration/Configuraion.json
(property 에서 build 시 copy 되도록 설정하자)
{
"TokenManagement": {
"Secret": "99ce883bfb15df8e5422d9f6e987500be6d98fb1e47a13aa12f29e38c3ad0a99",
"Issuer": "Tester",
"Audience": "Tester!GrpcService",
"AccessExpiration": 1440,
"RefreshExpiration": 10
}
}
Models/TokenManagement.cs
namespace Share.Models;
public class TokenManagement
{
public string Secret { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int AccessExpiration { get; set; }
public int RefreshExpiration { get; set; }
}
Utility/CustomConfigurationHelper.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using System.Diagnostics;
namespace Share.Utility;
public class CustomConfigurationHelper
{
private static IConfigurationBuilder _configBuilder;
private static readonly object _lock = new object();
public static IConfigurationRoot CreateConfigurationBuilder(string customJsonFile, string[] args = null)
{
var result = CreateConfigurationRoot(customJsonFile, args);
result = result?.Providers.Count() == 0 ? CreateConfigurationRoot(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, customJsonFile), args) : result;
return result;
}
private static IConfigurationRoot CreateConfigurationRoot(string customJsonFile, string[] args)
{
lock (_lock)
{
try
{
if (_configBuilder == null)
{
_configBuilder = new ConfigurationBuilder().AddJsonFile(customJsonFile, true, true);
}
else
{
var jsonConfigList = _configBuilder.Sources.Where(source => source is JsonConfigurationSource).OfType<JsonConfigurationSource>().ToList();
if (jsonConfigList.Exists(jsonSource => jsonSource.Path.Equals(customJsonFile, StringComparison.OrdinalIgnoreCase)))
{
return _configBuilder.Build();
}
_configBuilder = _configBuilder.AddJsonFile(customJsonFile, true, true);
}
if (args != null)
{
_configBuilder = _configBuilder.AddCommandLine(args);
}
return _configBuilder.Build();
}
catch (Exception e)
{
Debug.WriteLine(e);
return _configBuilder.Build();
}
}
}
}
Utility/JwtTokenHelper.cs
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.Dynamic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Share.Utility;
public class JwtTokenHelper
{
public string GenerateToken(string userId, string role = "user")
{
var config = CustomConfigurationHelper.CreateConfigurationBuilder("./CommonConfiguration/Configuration.json");
config = CustomConfigurationHelper.CreateConfigurationBuilder("./Configuration/Configuration.json");
var now = DateTime.UtcNow;
dynamic token = config.GetSection("TokenManagement").Get<ExpandoObject>();
int.TryParse(token.AccessExpiration, out int accessExpiration);
var claims = new[]
{
new Claim("Name", userId),
new Claim("Role", role),
new Claim("Ticks", now.AddMinutes(accessExpiration).Ticks.ToString())
};
var jwtToken = new JwtSecurityToken(
token.Issuer,
token.Audience,
claims,
expires: now.AddMinutes(accessExpiration),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)), SecurityAlgorithms.HmacSha512Signature));
return new JwtSecurityTokenHandler().WriteToken(jwtToken);
}
}
Protos/user.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option csharp_namespace = "GrpcInterface";
package user;
// The greeting service definition.
service User {
// Sends a greeting
rpc Login (LoginRequest) returns (LoginResponse);
rpc AuthHello(google.protobuf.Empty) returns (AuthHelloResponse);
}
// The request message containing the user's name.
message LoginRequest {
string id = 1;
}
// The response message containing the greetings.
message LoginResponse {
bool result = 1;
string accessToken = 2;
}
message AuthHelloResponse {
bool result = 1;
string message = 2;
}
Services/UserService.cs
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using GrpcInterface;
using Microsoft.AspNetCore.Authorization;
using Share.Utility;
namespace GrpcServerWithASPNet.Services
{
[Authorize]
public class UserService : User.UserBase
{
private readonly ILogger<UserService> _logger;
private readonly JwtTokenHelper _jwtTokenHelper;
public UserService(ILogger<UserService> logger)
{
_logger = logger;
_jwtTokenHelper = new JwtTokenHelper();
}
[AllowAnonymous]
public override Task<LoginResponse> Login(LoginRequest request, ServerCallContext context)
{
var httpContext = context.GetHttpContext();
return Task.FromResult(new LoginResponse
{
Result = true,
AccessToken = _jwtTokenHelper.GenerateToken(request.Id)
});
}
public override Task<AuthHelloResponse> AuthHello(Empty request, ServerCallContext context)
{
var httpContext = context.GetHttpContext();
var nameClaim = httpContext.User?.Claims?.SingleOrDefault(c => c.Type == "Name");
return Task.FromResult(new AuthHelloResponse
{
Message = $"Auth Success Hello {nameClaim.Value}",
Result = true
});
}
}
}
Program.cs
using GrpcServerWithASPNet.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Share.Models;
using Share.Utility;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
// Add services to the container.
builder.Services.AddGrpc();
var config = CustomConfigurationHelper.CreateConfigurationBuilder("./CommonConfiguration/Configuration.json");
config = CustomConfigurationHelper.CreateConfigurationBuilder("./Configuration/Configuration.json");
builder.Services.Configure<TokenManagement>(config.GetSection("TokenManagement"));
var token = config.GetSection("TokenManagement").Get<TokenManagement>();
var secret = Encoding.ASCII.GetBytes(token.Secret);
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
});
builder.Services.AddAuthorization();
builder.Services.AddGrpcReflection();
builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));
var app = builder.Build();
if(app.Environment.IsDevelopment())
app.MapGrpcReflectionService();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowAll");
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>().RequireCors("AllowAll");
app.MapGrpcService<UserService>().RequireCors("AllowAll");
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();
실행 방법
Login 을 먼저 실행해서 accessToken 을 얻어온다.
Request Metadata 을 확인 하자
Name 에 Authorization
Value 에 Bearer {accessToken}
그리고 invoke 해보자
아래와 같이 실행되었다면 정상 실행 된것이다.
만약 Request Metadata 에 아무것도 없이 실행 한다면 아래와 같이 나온다.
관련영상
반응형
'ASPNET > Grpc' 카테고리의 다른 글
gRPC - 로깅 및 진단 (0) | 2022.05.26 |
---|---|
gRPC - 인터셉터 (0) | 2022.05.25 |
gRPC - Configuration (0) | 2022.05.23 |
grpc test - grpcui (0) | 2022.05.20 |
grpc test - grpcurl (0) | 2022.05.19 |