SPMS_API/SPMS.API/Extensions/AuthenticationExtensions.cs
SEAN bf8f82e66c improvement: 로그아웃 시 Access Token 즉시 무효화 (#169)
- IJwtService/JwtService에 GetTokenInfo(JTI, 만료시간 추출) 추가
- LogoutAsync에 Redis 블랙리스트 로직 추가 (key: blacklist:{jti}, TTL: 남은 만료시간)
- AuthenticationExtensions OnTokenValidated에서 블랙리스트 체크
- 로그아웃 후 동일 Access Token 재사용 시 401 반환

Closes #169
2026-02-24 17:33:37 +09:00

73 lines
2.5 KiB
C#

using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using SPMS.Application.Interfaces;
using SPMS.Application.Settings;
namespace SPMS.API.Extensions;
public static class AuthenticationExtensions
{
public static IServiceCollection AddJwtAuthentication(
this IServiceCollection services,
IConfiguration configuration)
{
var jwtSettings = configuration.GetSection(JwtSettings.SectionName).Get<JwtSettings>()!;
services.Configure<JwtSettings>(configuration.GetSection(JwtSettings.SectionName));
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var tokenStore = context.HttpContext.RequestServices
.GetRequiredService<ITokenStore>();
var jti = context.Principal?.FindFirst("jti")?.Value;
if (!string.IsNullOrEmpty(jti))
{
var blacklisted = await tokenStore.GetAsync($"blacklist:{jti}");
if (blacklisted != null)
context.Fail("토큰이 무효화되었습니다.");
}
}
};
});
return services;
}
public static IServiceCollection AddAuthorizationPolicies(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("SuperOnly", policy =>
policy.RequireRole("Super"));
options.AddPolicy("ManagerOrAbove", policy =>
policy.RequireRole("Super", "Manager"));
});
return services;
}
}