홈으로 돌아가기

JWT 인증 구현 방법: 단계별 가이드

이 포괄적인 가이드는 최신 웹 API를 위한 JWT 인증 구현 방법을 설명합니다. 토큰 아키텍처, .NET 및 Node.js 예제를 통한 단계별 구현, 리프레시 토큰 로테이션 및 비대칭 서명을 포함한 보안 모범 사례, 그리고 피해야 할 일반적인 함정을 다룹니다.

JWT 인증 구현: 보안 모범 사례
Advertisement 728x90

.NET

JWT 인증: 완벽 구현 가이드

현대 웹 API와 애플리케이션에서 보안 인증 구현은 중요한 과제입니다. JSON Web Token(JWT)은 상태 비저장 인증의 업계 표준 솔루션으로 자리 잡았으며, 서버가 세션 상태를 유지하지 않고도 사용자 신원을 확인할 수 있게 해줍니다. 이 가이드를 마치면 JWT 기반 인증의 전체 아키텍처를 이해하고, 자체 애플리케이션에서 안전한 토큰 발급 및 검증을 구현할 수 있으며, 토큰 수명 주기 관리를 위한 프로덕션 준비 전략을 갖추게 됩니다. 어떤 기술 스택에서든 JWT 인증을 안전하게 구현할 수 있는 실무 지식을 얻게 될 것입니다.

학습 내용

JWT가 아키텍처적으로 어떻게 작동하는지, 왜 올바른 구현이 보안에 중요한지 이해하고, 인증 미들웨어 구성, 토큰 발급, API 엔드포인트 보호에 대한 실무 지식을 습득합니다. 마지막에는 자신의 프로젝트에서 JWT 인증을 구현할 때 보안 결정에 자신감을 가질 수 있게 됩니다.

Google AdInline article slot

JWT 인증 아키텍처 이해

코드에 들어가기 전에 JWT 인증이 어떻게 작동하는지 이해하는 것이 필수적입니다. JSON Web Token은 두 당사자 간의 클레임을 표현하는 간결하고 URL-안전한 수단입니다. 토큰은 점으로 구분된 세 개의 Base64URL 인코딩 부분(헤더, 페이로드, 서명)으로 구성됩니다(header.payload.signature).

JWT 인증 흐름은 명확한 패턴을 따릅니다: 클라이언트가 인증하고(일반적으로 로그인 엔드포인트를 통해), 서명된 토큰을 받은 후, 이후 요청의 Authorization 헤더에 해당 토큰을 포함시킵니다. 서버는 각 요청에서 토큰의 서명과 클레임을 검증하므로 시스템은 상태 비저장이 됩니다 — 서버 측 세션 저장소가 필요하지 않습니다.

JWT의 주요 구성 요소

구성 요소 목적 예시
헤더 서명 알고리즘 및 토큰 유형 지정 {"alg":"RS256","typ":"JWT"}
페이로드 클레임 포함(사용자 데이터, 만료, 발급자) {"sub":"user123","exp":1700000000}
서명 토큰 무결성 및 신뢰성 보장 헤더+페이로드의 암호화 해시(비밀 키 사용)

이 설계는 공유 세션 상태 유지가 비현실적인 분산 시스템 및 마이크로서비스에 JWT를 이상적으로 만듭니다.

Google AdInline article slot

JWT 인증 구현 방법: 단계별 가이드

다음 단계는 JWT 인증을 구현하는 프로덕션 준비 접근 방식을 설명합니다. 예제는 .NET과 Node.js를 사용하지만, 원칙은 모든 프로그래밍 언어에 적용됩니다.

1단계: 필요한 인증 패키지 설치

최신 프레임워크는 공식 JWT Bearer 인증 핸들러를 제공합니다. .NET의 경우 Microsoft.AspNetCore.Authentication.JwtBearer 패키지를 설치합니다. Node.js의 경우 jsonwebtoken을 사용합니다.

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

# Node.js
npm install jsonwebtoken bcryptjs dotenv

공식적이고 검증된 라이브러리에 의존하는 것이 필수적입니다 — 자체 암호화 서명 로직을 절대 구현하지 마십시오.

Google AdInline article slot

2단계: 인증 서비스 구성

인증 구성은 API가 들어오는 토큰을 어떻게 검증할지 정의합니다. 중요한 검증 매개변수는 다음과 같습니다:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,           // 토큰이 알려진 기관에서 왔는지 확인
            ValidateAudience = true,         // 토큰이 이 API를 대상으로 하는지 확인
            ValidateLifetime = true,         // 만료된 토큰 자동 거부
            ValidateIssuerSigningKey = true, // 암호화 서명 확인
            ValidIssuer = "https://your-identity-provider.com",
            ValidAudience = "https://your-api.com",
            IssuerSigningKey = publicKey     // 서명 검증을 위한 키
        };
    });

프로덕션에서는 네 가지 검증 플래그를 모두 true로 설정해야 합니다. 이 중 하나라도 비활성화하면 보안 취약점이 발생합니다. IssuerSigningKey는 PEM 파일에서 로드하거나, 프로덕션에서는 ID 공급자의 메타데이터 엔드포인트에서 자동으로 검색할 수 있습니다.

3단계: 인증 및 권한 부여 미들웨어 추가

미들웨어 구성 요소의 순서는 중요합니다 — 인증이 권한 부여보다 먼저 실행되어야 합니다:

app.UseAuthentication();  // JWT 검증 및 HttpContext.User 채움
app.UseAuthorization();   // 보호된 엔드포인트에서 [Authorize] 정책 적용

요청이 UseAuthentication에 도달하면 핸들러가 Authorization 헤더를 검사하고, JWT를 검증하며, 인증된 사용자를 나타내는 ClaimsPrincipal을 생성합니다. 이 객체는 이후 요청 파이프라인 전체에서 권한 부여 결정에 사용됩니다.

4단계: [Authorize] 속성으로 엔드포인트 보호

API 엔드포인트를 보호하려면 [Authorize] 속성이 필요합니다:

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet]
    public IActionResult GetSecureData()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return Ok($"인증된 사용자: {userId}");
    }
}

유효한 토큰이 없는 요청은 401 Unauthorized 응답을 받습니다. 인증은 성공했지만 필요한 권한이 없는 경우 API는 403 Forbidden을 반환합니다.

5단계: 토큰 발급 구현 (개발 전용)

프로덕션에서 토큰 발급은 OAuth 2.0 또는 OpenID Connect를 구현하는 전용 ID 공급자(IdP)의 역할입니다. API의 역할은 검증으로 제한됩니다.

그러나 개발 및 테스트를 위해 로컬 토큰 엔드포인트가 필요할 수 있습니다:

// jsonwebtoken을 사용한 Node.js 예제
router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ message: '잘못된 자격 증명' });
  }
  
  const token = jwt.sign(
    { id: user._id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '15m' } // 수명이 짧은 액세스 토큰
  );
  
  res.json({ token });
});

액세스 토큰은 수명이 짧아야 합니다 — 일반적으로 15분 — 토큰 도난 시 피해를 제한하기 위함입니다.

프로덕션 보안 모범 사례

LocalStorage에 토큰 저장 금지

웹 애플리케이션의 경우 JWT를 localStorage나 sessionStorage 대신 HttpOnly, Secure 쿠키에 저장하십시오. 이렇게 하면 XSS(교차 사이트 스크립팅) 공격이 토큰에 접근하는 것을 방지할 수 있습니다. 쿠키 구성에는 다음이 포함되어야 합니다:

HttpOnly: true       // JavaScript 접근 차단
Secure: true         // HTTPS를 통해서만 전송
SameSite: Lax 또는 Strict  // CSRF 보호

리프레시 토큰 로테이션 사용

액세스 토큰이 빠르게 만료되므로, 재인증 없이 사용자 세션을 유지하기 위해 리프레시 토큰 메커니즘을 구현하십시오:

  • 리프레시 토큰: 수명이 긴 토큰(예: 7일), 서버에 안전하게 저장
  • 액세스 토큰: 수명이 짧은 토큰(예: 15분), 각 요청과 함께 전송
  • 로테이션: 각 리프레시 요청 시 새 액세스+리프레시 쌍을 발급하고, 이전 리프레시 토큰을 무효화

이 접근 방식은 도난된 토큰의 영향을 완화하고 깔끔한 로그아웃 기능을 가능하게 합니다.

프로덕션에는 비대칭 서명(RS256/ES256) 사용

프로덕션에서는 HMAC(HS256) 대신 비대칭 알고리즘(RS256 또는 ES256)을 사용하십시오:

알고리즘 키 유형 사용 사례
HS256 대칭(공유 비밀) 단일 서비스 애플리케이션
RS256/ES256 비대칭(공개/개인 키) 다중 서비스 시스템, 마이크로서비스

비대칭 서명을 사용하면 여러 서비스가 공개 키를 사용하여 토큰을 검증할 수 있으며, 개인 키는 발급자만 보유합니다 — 최소 권한 원칙을 따릅니다.

모든 표준 클레임 검증

OAuth 2.0 액세스 토큰에는 다음 클레임이 필요하며 검증되어야 합니다:

  • iss(발급자) — 예상 기관과 일치
  • aud(대상) — API 식별자와 일치
  • exp(만료) — 토큰이 만료되지 않음
  • sub(주체) — 사용자 식별자
  • iat(발급 시간) — 토큰이 과거에 발급됨
  • jti(JWT ID) — 토큰 추적을 위한 고유 식별자

Redis를 사용한 상태 비저장 인증

JWT는 설계상 상태 비저장이지만, 토큰 철회를 구현하려면 저장소 계층이 필요합니다. Redis가 이상적인 솔루션을 제공합니다:

// 철회를 위한 토큰 메타데이터 저장
func (r *Redis) SetJTI(ctx context.Context, key, userID string, exp time.Time) error {
    return r.Client.Set(ctx, key, userID, time.Until(exp)).Err()
}

// 토큰이 철회되었는지 확인
func (r *Redis) GetUserByJTI(ctx context.Context, key string) (string, error) {
    return r.Client.Get(ctx, key).Result()
}

토큰의 고유 식별자(jti)를 토큰 만료와 일치하는 TTL로 Redis에 저장합니다. 이를 통해 즉시 로그아웃이 가능하고 손상된 토큰의 재사용을 방지할 수 있습니다.

피해야 할 일반적인 JWT 구현 실수

  1. 프로덕션에서 자체 토큰 발급: API는 토큰을 검증해야 하지 발급해서는 안 됩니다. 발급에는 전용 IdP를 사용하십시오.

  2. 약한 서명 키: openssl rand -base64 32로 생성된 암호학적으로 강력한 비밀(최소 32바이트)을 사용하십시오.

  3. 클레임 검증 비활성화: 프로덕션에서 ValidateIssuerValidateAudiencefalse로 설정하지 마십시오.

  4. 긴 토큰 수명: 액세스 토큰은 15분 이내에 만료되어야 합니다. 수명이 길수록 공격 표면이 증가합니다.

  5. 철회 메커니즘 부재: Redis 또는 유사한 저장소를 사용하여 로그아웃 및 토큰 철회를 구현하십시오.

출처

  • Duende Software. "A step by step JWT authentication example." Duende Software. 2025.
  • Microsoft Learn. "Configure JWT bearer authentication in ASP.NET Core." Microsoft. 2025.
  • Duende Software. "Implementing Token Authentication in Controller-Based ASP.NET Core Web APIs." Duende Software. 2026.
  • QuickNode. "How to Implement JSON Web Tokens (JWT) Authorization." QuickNode. 2025.
  • freeCodeCamp. "How to Build a Secure Authentication System with JWT and Refresh Tokens." freeCodeCamp. 2025.
  • Vonage. "JWT Authentication in Go with Gin." Vonage Developer. 2025.

— Editorial Team

Advertisement 728x90

다음 읽기