.NET
JWT 인증: 완벽 구현 가이드
현대 웹 API와 애플리케이션에서 보안 인증 구현은 중요한 과제입니다. JSON Web Token(JWT)은 상태 비저장 인증의 업계 표준 솔루션으로 자리 잡았으며, 서버가 세션 상태를 유지하지 않고도 사용자 신원을 확인할 수 있게 해줍니다. 이 가이드를 마치면 JWT 기반 인증의 전체 아키텍처를 이해하고, 자체 애플리케이션에서 안전한 토큰 발급 및 검증을 구현할 수 있으며, 토큰 수명 주기 관리를 위한 프로덕션 준비 전략을 갖추게 됩니다. 어떤 기술 스택에서든 JWT 인증을 안전하게 구현할 수 있는 실무 지식을 얻게 될 것입니다.
학습 내용
JWT가 아키텍처적으로 어떻게 작동하는지, 왜 올바른 구현이 보안에 중요한지 이해하고, 인증 미들웨어 구성, 토큰 발급, API 엔드포인트 보호에 대한 실무 지식을 습득합니다. 마지막에는 자신의 프로젝트에서 JWT 인증을 구현할 때 보안 결정에 자신감을 가질 수 있게 됩니다.
JWT 인증 아키텍처 이해
코드에 들어가기 전에 JWT 인증이 어떻게 작동하는지 이해하는 것이 필수적입니다. JSON Web Token은 두 당사자 간의 클레임을 표현하는 간결하고 URL-안전한 수단입니다. 토큰은 점으로 구분된 세 개의 Base64URL 인코딩 부분(헤더, 페이로드, 서명)으로 구성됩니다(header.payload.signature).
JWT 인증 흐름은 명확한 패턴을 따릅니다: 클라이언트가 인증하고(일반적으로 로그인 엔드포인트를 통해), 서명된 토큰을 받은 후, 이후 요청의 Authorization 헤더에 해당 토큰을 포함시킵니다. 서버는 각 요청에서 토큰의 서명과 클레임을 검증하므로 시스템은 상태 비저장이 됩니다 — 서버 측 세션 저장소가 필요하지 않습니다.
JWT의 주요 구성 요소
| 구성 요소 | 목적 | 예시 |
|---|---|---|
| 헤더 | 서명 알고리즘 및 토큰 유형 지정 | {"alg":"RS256","typ":"JWT"} |
| 페이로드 | 클레임 포함(사용자 데이터, 만료, 발급자) | {"sub":"user123","exp":1700000000} |
| 서명 | 토큰 무결성 및 신뢰성 보장 | 헤더+페이로드의 암호화 해시(비밀 키 사용) |
이 설계는 공유 세션 상태 유지가 비현실적인 분산 시스템 및 마이크로서비스에 JWT를 이상적으로 만듭니다.
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
공식적이고 검증된 라이브러리에 의존하는 것이 필수적입니다 — 자체 암호화 서명 로직을 절대 구현하지 마십시오.
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 구현 실수
프로덕션에서 자체 토큰 발급: API는 토큰을 검증해야 하지 발급해서는 안 됩니다. 발급에는 전용 IdP를 사용하십시오.
약한 서명 키:
openssl rand -base64 32로 생성된 암호학적으로 강력한 비밀(최소 32바이트)을 사용하십시오.클레임 검증 비활성화: 프로덕션에서
ValidateIssuer나ValidateAudience를false로 설정하지 마십시오.긴 토큰 수명: 액세스 토큰은 15분 이내에 만료되어야 합니다. 수명이 길수록 공격 표면이 증가합니다.
철회 메커니즘 부재: 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
아직 댓글이 없습니다.