-
ASP.NET CORE에서의 JWT 처리프로그래밍 2024. 10. 16. 23:45
ASP.NET CORE에서의 JWT 처리에 대해 알아보자.
JWT는 JSON Web Token으로 구조는 아래와 같다.
Header에는 사용할 알고리즘 및 타입 유형이
Payload에는 Claim 정보가
Signature에는 헤더에 선언된 알고리즘 private키가 선언된다.
보통 HS256 알고리즘 (HMAC)을 사용하므로 이에 대해 알아보면 아래와 같다.
HS256 알고리즘은 서버측서버 측 secret key를 이용해 암호화된 데이터를 발행하고 서버 측에 요청이 올 때 같은
secret key로 payload를 암호화 했을 경우 일치 여부를 따지는 논리이다.
JWT는 expire time, 즉, 만료시간을 갖는데 payload부분에 사용자 개인정보 또는 민감정보를 넣을 수 있으므로
가급적 짧게 가져가는 것이 좋다.
즉, 짧게 사용하고 갱신하는 방식을 권장하고 있다.
보안이 중요한 금융이나 헬스케어라면 15분 ~ 1시간
일반적인 웹 앱이라면 1일 ~ 1주일
정도 되겠다.
또한 JWT에 민감정보를 넣기 불가능한 상황이라면 Hash키를 담아서 처리하고 리퀘스트마다 DB나 Redis를 조회하도록
만들어도 되겠다.
JWT를 사용하면서 느낀 장점은 리퀘스트가 시작될 때 Session을 만들어서 처리하기 좋다는 것이다.
JWT를 사용하는 것은 WEB API에 주로 사용될 텐데 리퀘스트가 시작될 때 Session 처리기 등을 만들어 Middleware로
사용하면 코드 공통화를 할 수 있는 장점이 있다.
이제 작성할 코드를 보자.
// JWT Authentication 설정 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer= issuer, ValidAudience= audience, IssuerSigningKey = new SymmetricSecurityKey(key) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (accessToken.xIsNotEmpty() && (path.StartsWithSegments("/chatHub")) { context.Token = accessToken; } return Task.CompletedTask; }, OnAuthenticationFailed = c => { c.NoResult(); c.Response.StatusCode = StatusCodes.Status401Unauthorized; c.Response.ContentType = "text/plain"; return c.Response.WriteAsync(c.Exception.ToString()); }, OnChallenge = context => { context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("You are not Authorized"); }, OnForbidden = context => { context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("You are not authorized to access this resource"); }, }; });
TokenValidationParameters 선언을 보면 각종 설정들을 볼 수 있다.
IssuerSigningKey는 앞으로 발급할 Token의 키와 같은 값이다.
나머지는 Jwt Bearer 이벤트로 Signalr Hub에 대한 설정을 위해 chatHub에 대한 처리 부분이 있는 것을 확인할 수 있다.
Failed, Challenge, Forbidden 3가지 유형에 대하여 각각 처리하고 있다.
다음으로 WebApplication 설정을 할 때 아래와 같이 선언한다.
app.UseAuthentication(); app.UseAuthorization();
위와 같으며 순서가 중요하다.
순서가 바뀌면 실행 중 오류 또는 인증이 되지 않는 문제가 발생할 것이다.
JWT를 위한 설정을 진행 하였으므로 이제 발급 부분을 보자.
public static string GenerateJwtToken(JwtConfig jwtConfig, DateTime? expire, string email, string name, string picture) { var claims = new[] { new Claim(AppClaimTypes.Email, email), new Claim(AppClaimTypes.Name, name ?? string.Empty), new Claim(AppClaimTypes.Picture, picture ?? string.Empty), }; var secretKey = jwtConfig.Secret; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: jwtConfig.Issuer, audience: jwtConfig.Audience, claims: claims, expires: expire, signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); }
위 코드에서 claim을 설정하고 설정 부분과 동일한 secret key를 이용하여 HMAC SHA256으로 암호화하고 있다.
위와 같이 발급된 JWT가 설정 부분의 Event를 통과하여 처리되게 된다.
물론 issuer, audience도 설정 부분과 동일해야 한다.
추가로 위와 같은 내용을 통해 Middleware 또는 Filter를 만들 수 있다.
필자의 경우 Filter로 처리하였는데 코드는 아래와 같다.
public class SessionFilter : IAsyncActionFilter { private readonly ISessionContext _session; private readonly IHttpContextAccessor _accessor; /// <summary> /// ctor /// </summary> /// <param name="accessor"></param> /// <param name="session"></param> public SessionFilter(IHttpContextAccessor accessor, ISessionContext session) { _accessor = accessor; _session = session; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var email = _accessor.HttpContext!.User.FindFirstValue(AppClaimTypes.Email); var db = _session.DbContext.xAs<WellnessDbContext>(); var account = await db.Users .AsNoTracking() .Include(m => m.Setting) .Where(m => m.Email == email)) .FirstAsync(); if (account.xIsNotEmpty()) { _accessor.HttpContext.xTryGetRequestHeader("Authorization", out var v); if (v.ToString().Replace("bearer", "").Replace("Bearer", "").Trim() != profile.Token) { context.Result = new UnauthorizedResult(); return; } } _session.User.xAs<ISessionUserInitializer>().CurrentInitialize(account); using (LogContext.PushProperty("AccountInfo", $"{account.Id}")) { await next(); } } }
위 코드는 요청된 리퀘스트에 대한 Session을 생성하기 위한 코드이다. 따라서, 별도의 처리없이 ISessionContext를 생성하여 의존성 주입을 통해 각각의 Service 또는 Repository에 주입할 수 있다.
위와 같은 Filter는 Controller 또는 Method에 Attribute로 선언할 수 있고 Application Lifecycle에 따라 호출되게 된다.
위 코드는 Method보다는 Controller에 선언하는 방법을 선택하였다.
AppClaimTypes는 필자가 생성한 코드로 일반적으로는 JwtClaimTypes를 사용하는 것이 좋다.
위와 같이 처리한다면 인증 정보 또는 사용자 정보를 매번 Controller나 Method에서 처리할 필요 없이 불필요한 코드를 줄이며 효과적으로 작업할 수 있다.
도움이 되기를 바라며...
'프로그래밍' 카테고리의 다른 글
AWS Secrets Manager에 대하여 (0) 2024.10.21 ASP.NET CORE Serilog 설정 및 사용 (0) 2024.10.17 SignalR Redis Backplane (0) 2024.10.16 EF CORE의 상태 변경 (1) 2024.10.16 예약, 예매 시스템에 대한 고찰-2 (6) 2024.10.16