JWT 기반 인증 구현과 옵션 설정 가이드

JWT의 기본 개념

JSON Web Token은 당사자 간 정보를 JSON 객체로 안전하게 전달하기 위한 개방형 규격(RFC 7519)이다. 디지털 서명을 통해 무결성과 발신자 신원을 보장하며, 사용자 인증·권한 부여·정보 교환에 널리 인다.

토큰 구성 요소

JWT는 점(.)으로 구분된 세 부분으로 이루어진다.

구성설명
Header알고리즘과 토큰 유형 정보
Payload사용자 식별자, 권한, 만료 시각 등 클레임
SignatureHeader와 Payload를 비밀키로 서명한 값

최종 형태: Base64Url(Header).Base64Url(Payload).Base64Url(Signature)

.NET Core 환경 설정

필요 패키지 설치

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package System.IdentityModel.Tokens.Jwt

애플리케이션 설정

appsettings.json에 인증 파라미터를 정의한다.

{
  "AuthSettings": {
    "Key": "MinimumSixteenCharacters!",
    "Issuer": "MyApplication",
    "Recipient": "WebClients",
    "DurationMinutes": 60
  }
}

서비스 등록 및 파이프라인 구성

// Program.cs (.NET 6+)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

var authConfig = builder.Configuration.GetSection("AuthSettings");
var signingKey = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(authConfig["Key"]!));

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(cfg => {
        cfg.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signingKey,
            ValidateIssuer = true,
            ValidIssuer = authConfig["Issuer"],
            ValidateAudience = true,
            ValidAudience = authConfig["Recipient"],
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(5)
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

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

토큰 발급 서비스 구현

public interface IAccessTokenGenerator {
    string CreateToken(UserIdentity identity);
}

public class AccessTokenGenerator : IAccessTokenGenerator {
    private readonly IConfiguration _config;

    public AccessTokenGenerator(IConfiguration config) {
        _config = config;
    }

    public string CreateToken(UserIdentity identity) {
        var key = Encoding.UTF8.GetBytes(_config["AuthSettings:Key"]!);
        var securityKey = new SymmetricSecurityKey(key);
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim> {
            new(JwtRegisteredClaimNames.Sub, identity.UserId.ToString()),
            new(JwtRegisteredClaimNames.UniqueName, identity.Username),
            new(JwtRegisteredClaimNames.Email, identity.Email),
            new("role", identity.Role),
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var tokenDescriptor = new JwtSecurityToken(
            issuer: _config["AuthSettings:Issuer"],
            audience: _config["AuthSettings:Recipient"],
            claims: claims,
            notBefore: DateTime.UtcNow,
            expires: DateTime.UtcNow.AddMinutes(
                double.Parse(_config["AuthSettings:DurationMinutes"]!)),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
    }
}

보호된 엔드포인트 구성

[ApiController]
[Route("[controller]")]
public class SessionController : ControllerBase {
    private readonly IAccessTokenGenerator _tokenGenerator;

    public SessionController(IAccessTokenGenerator generator) {
        _tokenGenerator = generator;
    }

    [HttpPost("authenticate")]
    public ActionResult<AuthResponse> Authenticate(LoginModel model) {
        // 실제로는 데이터베이스 검증 수행
        if (!ValidateCredentials(model)) {
            return Unauthorized();
        }

        var identity = new UserIdentity {
            UserId = 1001,
            Username = model.Username,
            Email = "user@example.com",
            Role = "StandardUser"
        };

        var token = _tokenGenerator.CreateToken(identity);
        return Ok(new AuthResponse { AccessToken = token });
    }

    [HttpGet("admin-only")]
    [Authorize(Roles = "SystemAdmin")]
    public IActionResult AdminResource() {
        return Ok(new { Data = "관리자 전용 정보" });
    }
}

JwtBearerOptions 상세 설정

증 파라미터

속성기능권장 설정
TokenValidationParameters발행자, 수신자, 서명키, 유효기간 등 검증 규칙 정의모든 검증 항목 명시적 활성화
AuthorityOIDC 공개 메타데이터를 자동 수집할 인증 서버 주소신뢰할 수 있는 ID 공급자 URL
Audience토큰의 의도된 수신자 식별자API 식별자와 일치

메타데이터 관리

속성기능권장 설정
MetadataAddress공개 메타데이터 문서(.well-known) 직접 지정Authority로 자동 생성 시 불필요
RequireHttpsMetadata메타덷 주소의 HTTPS 강제 여부운영 환경 필수 true
AutomaticRefreshInterval메타데이터 자동 갱신 주기기본 24시간, 키 교체 주고 고려
RefreshOnIssuerKeyNotFound서명키 미발견 시 자동 재조회true (키 롤오버 대응)

통신 및 디버깅

속성기능권장 설정
BackchannelTimeout메타데이터 서버 통신 타임아웃네트워크 환경에 따라 30~60초
Backchannel커스텀 HttpClient 인스턴스 주입프록시/인증서 필요 시 사용
IncludeErrorDetails인증 실패 시 상세 오류 응답 포함개발 true, 운영 false
SaveToken검증된 토큰을 HttpContext에 보존다운스트림 API 호출 시 true

클레임 및 호환성

속성기능권장 설정
MapInboundClaimsJWT 표준 클레임을 .NET ClaimTypes으로 매핑표준 라이브러리 연동 시 true
TokenHandlers비동기 토큰 처리기 목록JsonWebTokenHandler 우선 사용
UseSecurityTokenValidators레거시 검증기 강제 사용새 프로젝트는 false 유지

실용적 구성 예시

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opts => {
        // 검증 규칙
        opts.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["AuthSettings:Key"]!)),
            ValidateLifetime = true,
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["AuthSettings:Issuer"],
            ValidateAudience = true,
            ValidAudience = builder.Configuration["AuthSettings:Recipient"],
            ClockSkew = TimeSpan.Zero
        };

        // 메타데이터 보안
        opts.RequireHttpsMetadata = !builder.Environment.IsDevelopment();

        // 이벤트 처리
        opts.Events = new JwtBearerEvents {
            OnAuthenticationFailed = ctx => {
                // 로깅 또는 모니터링 연동
                return Task.CompletedTask;
            },
            OnTokenValidated = ctx => {
                // 추가 권한 검증 또는 감사 로깅
                return Task.CompletedTask;
            }
        };
    });

클라이언트 요청 예시

// HTTP 헤더에 토큰 포함
GET /api/protected-resource HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

주의사항

  • Payload는 Base64Url 인코딩될 뿐 암호화되지 않으므로 민감 정보 저장 금지
  • 대칭키(HMAC)는 서버 간 공유, 비대칭키(RSA/ECDSA)는 공개키로 검증
  • 토큰 폐기(로그아웃)는 블랙리스트 또는 짧은 만료 시간 + 재발급 전략으로 구현
  • HTTPS 미사용 시 토큰 탈취 위험, 반드시 TLS 적용

태그: jwt .NET Core authentication JwtBearer TokenValidationParameters

6월 11일 18:30에 게시됨