본문 바로가기

테크 IT 꿀팁

JWT 인증 시스템 : Node.js 기반 안전한 API 개발

현대의 웹 및 모바일 애플리케이션은 사용자와 서버 간의 안전하고 효율적인 통신이 필수적입니다. 특히 RESTful API 기반의 서비스에서는 사용자의 신원을 확인하고 접근 권한을 관리하는 '인증(Authentication)' 메커니즘이 중요하게 다뤄집니다. 기존의 세션 기반 인증 방식은 서버가 사용자 세션 상태를 저장해야 하므로, 서버 확장에 어려움이 있었습니다. 이러한 한계를 극복하고 등장한 것이 바로 JWT 인증 시스템입니다. JWT(JSON Web Token)는 클라이언트와 서버 간에 정보를 안전하게 주고받기 위해 사용되는 자체 포함형 토큰으로, 서버가 사용자 상태를 저장하지 않는 '무상태(Stateless)' 방식으로 인증을 처리합니다. 이 글에서는 JWT 인증 시스템의 기본 개념, Access Token과 Refresh Token을 활용한 보안 강화 방안, 그리고 Node.js 환경에서 JWT를 발급하고 검증하는 실제 구현 방법에 대해 자세히 알아보겠습니다.

 

JWT 인증 시스템 AI 이미지 (직접 제작)
출처 : JWT 인증 시스템을 근거로 한 AI 생성 이미지 (wrtn 직접 제작)

 

1. JWT란 무엇인가? - 구조와 작동 원리

JWT 인증 시스템의 핵심인 JWT는 RFC 7519 표준에 정의된 'JSON 웹 토큰'입니다. 이는 Header, Payload, Signature라는 세 부분으로 구성되며, 각 부분은 점(.)으로 구분되어 Header.Payload.Signature 형태로 인코딩됩니다. 첫 번째 부분인 Header는 토큰의 타입(JWT)과 서명에 사용된 알고리즘(예: HMAC SHA256 또는 RSA) 정보를 JSON 객체 형태로 담고 있습니다. 두 번째 부분인 Payload는 클레임(Claim)이라고 불리는 실제 정보를 포함합니다. 사용자 ID, 만료 시간, 발급자 등 토큰에 담고자 하는 데이터가 여기에 기록됩니다. 마지막 세 번째 부분인 Signature는 Header와 Payload를 Base64Url로 인코딩한 후, 서버가 가지고 있는 '비밀 키(Secret Key)'를 사용하여 Header에 명시된 암호화 알고리즘으로 서명한 값입니다. 이 서명을 통해 토큰이 위변조되지 않았음을 검증할 수 있습니다. JWT는 이렇게 토큰 자체에 모든 인증 정보가 포함되어 있어, 서버가 별도로 세션을 유지할 필요 없이 토큰만으로 유효성을 검사할 수 있다는 것이 가장 큰 특징입니다.

 

2. Access Token과 Refresh Token을 활용한 JWT 인증의 강화

JWT 인증 시스템을 실제 서비스에 적용할 때는 주로 Access Token과 Refresh Token이라는 두 가지 유형의 토큰을 함께 사용합니다. Access Token은 실제로 API 요청에 사용되는 토큰으로, 짧은 만료 시간(예: 15분~1시간)을 가집니다. 이는 Access Token이 탈취되더라도 공격자가 시스템에 접근할 수 있는 시간을 최소화하기 위함입니다. Refresh Token은 Access Token이 만료되었을 때, 사용자에게 재로그인을 요구하지 않고 새로운 Access Token을 발급받기 위한 목적으로 사용됩니다. Refresh Token은 Access Token보다 긴 만료 시간(예: 수 일~수 개월)을 가지며, Access Token에 비해 상대적으로 보안에 더욱 민감하므로 보통 HttpOnly, Secure 속성을 가진 쿠키에 저장하는 것이 일반적입니다. 인증 흐름은 다음과 같습니다. 사용자가 로그인에 성공하면 서버는 Access Token과 Refresh Token을 모두 발급하여 클라이언트에게 전송합니다. 클라이언트는 Access Token을 HTTP 요청의 Authorization 헤더에 담아 API 요청을 보내고, Refresh Token은 안전하게 보관합니다. Access Token이 만료되면 클라이언트는 Refresh Token을 서버로 보내 새로운 Access Token을 요청합니다. 서버는 Refresh Token의 유효성을 검사한 후 새로운 Access Token을 발급해 줍니다. 이 구조는 보안성과 사용자 편의성을 동시에 고려한 JWT 인증 시스템의 표준적인 활용 방법입니다.

 

3. Node.js에서 JWT 토큰 발급 및 검증 구현

Node.js 환경에서 JWT 인증을 구현하는 것은 jsonwebtoken 라이브러리를 사용하면 매우 간단합니다. 먼저 npm을 통해 라이브러리를 설치합니다 : npm install jsonwebtoken.

 

1. 토큰 발급 (로그인 시) : 사용자가 로그인에 성공하면 jwt.sign() 메서드를 사용하여 토큰을 발급합니다.

javascript

const jwt = require('jsonwebtoken');
const secretKey = process.env.JWT_SECRET_KEY; // 보안을 위해 환경 변수 사용

const payload = { userId: user.id, username: user.username };
const accessToken = jwt.sign(payload, secretKey, { expiresIn: '1h' }); // 1시간 유효
const refreshToken = jwt.sign(payload, secretKey, { expiresIn: '14d' }); // 14일 유효

// 클라이언트에 Access Token과 Refresh Token 전송
res.json({ accessToken, refreshToken });

2. 토큰 검증 (API 요청 시 미들웨어) : 보호된 API 라우트에 접근할 때마다 미들웨어를 사용하여 토큰의 유효성을 검증합니다.

javascript

const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>

    if (token == null) return res.sendStatus(401); // 토큰 없음

    jwt.verify(token, secretKey, (err, user) => {
        if (err) return res.sendStatus(403); // 토큰 유효하지 않음 (만료 또는 위변조)
        req.user = user; // 유효한 토큰일 경우 user 정보를 req 객체에 추가
        next(); // 다음 미들웨어 또는 라우트 핸들러로
    });
};

// 보호된 라우트에 적용 예시
// app.get('/protected', authenticateToken, (req, res) => { /* ... */ });

이러한 방식으로 Node.js 서버는 클라이언트로부터 받은 토큰의 유효성을 검증하여 요청을 처리합니다.

 

4. JWT 보안 강화 모범 사례

JWT 인증 시스템은 무상태 기반으로 편리하지만, 몇 가지 보안 취약점을 가질 수 있으므로 모범 사례를 따르는 것이 중요합니다. 첫째, 비밀 키(Secret Key) 관리는 철저해야 합니다. 비밀 키는 환경 변수에 저장하고 절대 코드에 하드코딩하지 않으며, 주기적으로 교체(Key Rotation)하는 것을 권장합니다. 비밀 키가 노출되면 공격자가 유효한 토큰을 위조할 수 있습니다. 둘째, HTTPS 사용은 필수적입니다. 토큰이 평문으로 전송될 경우 네트워크 스니핑을 통해 탈취될 수 있으므로, 항상 HTTPS(TLS/SSL) 암호화 통신을 사용해야 합니다. 셋째, Access Token의 짧은 만료 시간 설정입니다. Access Token은 탈취 시 공격자가 시스템에 접근할 수 있는 직접적인 수단이므로, 만료 시간을 짧게 설정하여 노출 위험을 최소화해야 합니다. 넷째, Refresh Token의 안전한 관리 및 폐기입니다. Refresh Token은 Access Token을 재발급받는 중요한 토큰이므로, 클라이언트 측에서는 HttpOnly, Secure 속성의 쿠키에 저장하여 XSS(Cross-Site Scripting) 공격으로부터 보호해야 합니다. 또한, 사용자가 로그아웃하거나 계정이 탈취된 경우 Refresh Token을 서버에서 즉시 무효화할 수 있는 메커니즘을 구현해야 합니다.

 

5. JWT 인증의 한계와 다른 인증 방식과의 비교

JWT 인증은 무상태 기반의 확장성, 다양한 클라이언트 지원, 빠른 검증 속도 등 많은 장점을 제공하지만, 몇 가지 한계점도 존재합니다. 가장 큰 한계는 Access Token의 실시간 폐기가 어렵다는 점입니다. Access Token은 만료될 때까지 유효하므로, 탈취되더라도 강제로 무효화하기 어렵습니다 (이를 위해 토큰 블랙리스트를 사용하기도 하지만, 이는 다시 서버에 상태를 저장해야 하므로 JWT의 무상태 원칙과 상충될 수 있습니다). 또한, 토큰의 페이로드에 민감한 정보가 직접 담기지 않도록 주의해야 하며, 토큰이 커지면 HTTP 요청 헤더의 크기가 증가하여 성능에 미묘한 영향을 줄 수 있습니다. 세션 기반 인증은 서버에 사용자 상태를 저장하므로 서버 확장이 복잡하고 서버 리소스 부담이 있지만, 토큰을 즉시 무효화하는 것이 용이합니다. OAuth 2.0은 인증이 아닌 '인가(Authorization)' 프레임워크로, JWT는 OAuth 2.0에서 Access Token의 구현 방식으로 자주 사용됩니다. 궁극적으로 JWT 인증 시스템은 무상태와 확장성을 중요시하는 현대 API 개발에 매우 적합한 솔루션이지만, 그 특성과 한계를 정확히 이해하고 보안 모범 사례를 철저히 지키며 구현하는 것이 중요합니다.

 

마무리하며 -

JWT 인증 시스템은 현대 웹 및 모바일 API 개발에서 무상태 기반의 확장 가능하고 안전한 인증을 구현하는 데 핵심적인 기술입니다. Node.js 환경에서 jsonwebtoken 라이브러리를 활용하면 Access Token과 Refresh Token의 발급 및 검증을 효과적으로 구현할 수 있습니다. 비밀 키 관리, HTTPS 통신, 적절한 토큰 만료 시간 설정, Refresh Token의 안전한 관리 및 폐기 등 보안 모범 사례를 철저히 지킴으로써 JWT 인증 시스템의 장점을 최대한 활용하고, 안전하고 신뢰할 수 있는 서비스를 개발할 수 있습니다.