로그인 후 사용자 인증 관리를 하는 방법으로는 여러 가지가 있지만, 개인적으로 가장 먼저 생각 나는 방식들로는 서버 세션에 로그인 상태 저장, JWT 토큰 인증 방식이 있습니다. 제가 프로젝트에서 사용한 방식은 JWT 토큰 인증 방식입니다. 이 경험에 대해서 이야기하기 전에 간단히 해당 두 가지 방법의 개념에 대해서 소개하겠습니다.
서버 세션에 로그인 상태 저장이란?
서버 세션(Session) 방식은 사용자가 로그인하면 서버가 해당 사용자에 대한 정보를 메모리나 데이터베이스에 저장하고, 사용자의 브라우저에는 세션 ID가 담긴 쿠키를 전달하는 방식입니다. 이후 사용자는 요청을 보낼 때마다 세션 ID를 함께 전송하고, 서버는 이 세션 ID를 통해 해당 사용자가 누구인지 식별합니다.
이 방식의 장점은 서버가 인증 정보를 직접 관리하기 때문에 비교적 안전하고 구현이 단순하다는 점입니다. 하지만 단점으로는 서버에 상태를 저장해야 하므로 서버 간의 세션 공유가 필요하고, 확장성에 불리할 수 있다는 점이 있습니다. 특히 서버가 여러 대일 경우, 세션을 중앙에 저장하거나 sticky session 등의 처리를 해야 합니다.
JWT 토큰이란?
JWT(JSON Web Token) 방식은 인증 정보를 클라이언트에 담아 보내는 방식입니다. 사용자가 로그인하면 서버는 인증 정보를 담은 JWT 토큰을 생성해서 클라이언트에 전달하고, 클라이언트는 이 토큰을 이후 요청마다 함께 전송함으로써 인증을 유지합니다.
JWT는 보통 Header, Payload, Signature 세 부분으로 구성되며, Payload에는 사용자 정보와 토큰의 만료 시간 등의 정보가 들어 있습니다. Signature는 토큰의 위변조를 방지하기 위해 사용되며, 서버가 가지고 있는 secret key로 서명됩니다.
이 방식의 장점은 서버가 상태를 저장하지 않아도 되기 때문에 확장성이 좋고, 마이크로서비스 아키텍처에 적합하다는 점입니다. 하지만 단점으로는 토큰 자체가 클라이언트에 저장되므로 노출되었을 때 위험할 수 있고, 토큰이 유효한 동안에는 서버에서 강제로 무효화시키기 어렵다는 점이 있습니다.
이제 본격적으로 제가 프로젝트에서 JWT를 활용한 방법에 대해서 이야기하겠습니다.
JWT를 활용한 인증 구현
우선 저는 제가 한 방식이 좋은 방식이라고 말하지 못할 것 같습니다. 실무 경험이 없는 아직 대학생이기에 실제 회사에서는 어떻게 사용자 인증을 하고, 보안을 유지하는지 모르기 때문이죠.
그래서 저는 우선 "기본적인 보안만 고려해보자."라는 생각으로 시작하였습니다. 제가 고려한 보안 사항은 XSS 공격에 대한 방어, 중간자 공격 방어였습니다. 따라서 JWT 토큰과 HttpOnly 쿠키를 결합하여 사용하였습니다.
사용자가 로그인을 하게 되면 인증과 함께 accessToken
, refreshToken
을 쿠키로 전송합니다. 이때 아래와 같이 보안 설정을 하여 전송하였습니다.
// 쿠키 보안 설정
res.cookie("accessToken", accessToken, {
httpOnly: true, // XSS 공격 방어
secure: true, // HTTPS에서만 전송
sameSite: "strict", // CSRF 공격 방어
maxAge: 15 * 60 * 1000, // 15분 만료
});
위 코드에서 알 수 있듯이 accessToken
의 유효기간은 15분으로 짧으면서 그렇다고 너무 짧지 않게 설정하였습니다.
그리고 프론트엔드에서는 10분마다 /auth/refresh
엔드포인트를 호출하여 accessToken
을 자동으로 갱신해주는 로직을 추가하였습니다. 이때도 브라우저는 쿠키를 자동으로 함께 전송하기 때문에 사용자는 별도의 로그인 없이 계속해서 서비스를 사용할 수 있습니다.
// 10분마다 토큰 재발급
setInterval(async () => {
const res = await api.auth.refresh();
if (!res.success) {
await logout();
}
}, 10 * 60 * 1000);
토큰을 갱신할 때도 마찬가지로 JavaScript를 통한 토큰 탈취를 방지를 위해 HttpOnly 쿠키를 사용하였습니다. 또한 sameSite: 'strict'
속성을 통해 외부 사이트에서의 요청에는 쿠키가 전송되지 않기 때문에 CSRF 공격도 어느 정도 방어할 수도 있습니다.
이와 같은 구조 덕분에 사용자는 로그인 이후 별도로 토큰을 관리할 필요 없이 서비스에 접속할 수 있고, 서버는 쿠키에 담긴 토큰을 기반으로 인증 상태를 판단할 수 있습니다.
하지만 JWT는 서버가 상태를 저장하지 않는 구조이기 때문에, 발급한 토큰을 중간에 강제로 무효화시키는 데 한계가 있습니다. 예를 들어 사용자가 로그아웃을 했거나 계정이 탈취되어 토큰을 더 이상 유효하게 두고 싶지 않은 상황에서도, 토큰 자체는 만료시간 전까지 계속 유효하게 작동합니다.
이 문제를 해결하기 위해 토큰 블랙리스트(Token Blacklist) 라는 개념을 도입하였습니다. 서버에서 명시적으로 “이 토큰은 더 이상 유효하지 않다”는 목록을 유지하는 방식입니다. 사용자가 로그아웃하거나 강제 로그아웃되어야 하는 경우 해당 토큰을 블랙리스트에 등록하고, 이후 모든 요청에서 이 블랙리스트를 조회해 토큰이 포함되어 있는지 확인합니다.
블랙리스트는 보통 Redis 같은 인메모리 데이터베이스에 저장해서 빠르게 조회할 수 있고, 토큰의 만료 시간에 맞춰 자동으로 삭제되도록 설정할 수 있습니다. 다만 이 방식은 JWT의 장점 중 하나인 무상태성(stateless) 을 포기하는 방식이라, 트레이드오프에 대한 고민이 필요합니다. 그래서 이번 프로젝트에서는 블랙리스트 관련 코드는 작성해 두었지만, 실제로 활용할지는 아직 고민 중입니다.
아직 보안에 대해 배워야 할 점이 많습니다. 혹시 글을 보시고 더 나은 방법이나 보완할 점, 또는 좋은 아이디어가 있는 경우 댓글로 알려주시면 감사하겠습니다!
'Web Development' 카테고리의 다른 글
왜 Transaction이 필요하고 중요할까? (3) | 2025.08.17 |
---|---|
Google OAuth 다중 계정 로그인 문제 해결 (5) | 2025.08.07 |
Web Socket(웹 소켓)과 HTTP (0) | 2025.02.11 |
순환 참조 문제 해결(NestJS) (0) | 2024.04.10 |
비동기 함수(async function), Promise 란? (0) | 2024.03.12 |