Home JWT와 세션
Post
Cancel

JWT와 세션

개요

HelloMaple 백오피스 SSO(OAuth) 로그인을 개발할 때 로그인 상태를 유지하기 위해 JWT를 사용했는데 보안 검수에서 세션 방식을 이용할 것을 권고받아 세션 방식으로 변경한 경험을 정리하려고 한다.

JWT? 세션?

JWT와 세션에 대하여 간략하게 알아보자.

JWT

JWT은 JSON 객체를 사용하여 정보를 전달한다. JWT의 구조는 HEADER.PAYLOAD.SIGNATURE로 구성되어 있으며 각각의 역할은 아래와 같다.

HEADER

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}
  • 토큰의 유형과 서명에 사용되는 알고리즘 정보를 담고 있다.
  • Base64로 인코딩되어 있다.

PAYLOAD

1
2
3
4
5
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
  • 인증에 필요한 정보를 담고 있다.
  • Base64로 인코딩되어 있으며 누구나 디코딩할 수 있기 때문에 비밀번호 등 민감한 데이터를 담으면 안 된다.

SIGNATURE

1
2
3
4
5
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)
  • 헤더와 페이로드를 각각 Base 64로 인코딩한 후, 이를 연결한 문자열을 비밀 키와 함께 서명 알고리즘으로 암호화한 값을 다시 Base 64로 인코딩한 값이다.
  • JWT의 무결성 체크를 하는 데 사용한다.

여기를 클릭하면 JWT를 직접 생성해 볼 수 있다. JWT의 동작 방식은 아래와 같다.

  1. 사용자가 로그인에 성공하면 서버는 사용자의 정보를 페이로드에 담아 JWT를 생성하고, 이를 클라이언트에게 전달한다.
  2. 클라이언트는 쿠키나 로컬 스토리지에 저장한다.
  3. 서버로 요청을 보낼 때마다 HTTP 헤더의 Authorization 필드나 쿠키를 이용해 JWT를 포함시켜 전송한다.
  4. 서버에서는 JWT의 서명을 검증하고 페이로드를 확인해 인증 처리를 한다.

세션?

사용자의 인증 상태를 별도 저장소에 저장하고 관리하는 방식이다. 세션의 동작 방식은 아래와 같다.

  1. 사용자가 로그인에 성공하면 서버는 고유한 세션 ID를 생성한 후 해당 값과 매핑되는 사용자의 정보를 저장소에 저장한다.
  2. 1번에서 발급한 세션 ID를 사용자에게 쿠키로 전달한다.
  3. 클라이언트가 서버로 요청을 보낼 때마다 쿠키로 세션 ID를 포함시켜 전송한다.
  4. 서버는 저장소에서 세션 ID에 해당하는 사용자 정보가 있는지 확인해 인증 처리를 한다.

JWT vs 세션

아래는 Chat GPT에게 JWT와 세션 차이를 문의한 결과를 표로 정리했다.

구분JWT (Json Web Token)Session (세션)
저장 위치클라이언트 측에 저장 (주로 쿠키나 로컬 스토리지)서버 측에 저장 (세션 ID는 클라이언트 측 쿠키에 저장)
인증 정보 관리토큰 자체에 사용자의 인증 정보 포함 (self-contained)서버에 저장된 세션 정보와 클라이언트가 주고받는 세션 ID로 인증
상태 관리무상태(Stateless): 서버는 JWT를 직접 저장하지 않음상태 기반(Stateful): 서버에서 세션 상태를 유지함
구조Header, Payload, Signature로 구성됨클라이언트와 서버 간의 세션 ID로 참조되는 서버 측 저장소에 데이터 저장
서명서명이 포함되어 있어 변조 여부 확인 가능서명 없음
유효성 검증클라이언트가 보낸 토큰을 서버에서 서명 검증 후 정보 확인서버에서 세션 ID를 기반으로 세션 데이터 확인
토큰 갱신토큰 만료 시 갱신 필요 (액세스 토큰 + 리프레시 토큰)세션은 만료 시간 전까지 자동으로 유효
만료 시간JWT는 발급 시점에 만료 시간 설정 가능 (exp 필드)서버 측에서 세션 만료 시간 설정 가능, 자동 연장 가능
보안 위험클라이언트에 저장된 JWT가 탈취되면, 탈취된 토큰 유효기간 동안 사용 가능세션 ID가 탈취되면 동일한 위험성 (세션 하이재킹)
서버 부하서버는 토큰을 저장하지 않으므로 부하가 적음 (무상태성 유지)서버는 각 클라이언트의 세션 데이터를 저장하므로 서버 부하가 증가 가능
크기JWT는 서명과 함께 전송되므로 비교적 크기가 큼세션 쿠키는 ID만 저장하므로 크기가 작음
로그아웃 처리JWT는 클라이언트 측 토큰 삭제 필요, 서버에서 강제 무효화 어려움서버 측에서 세션 무효화 가능 (로그아웃 처리 용이)
사용 예시API 인증, OAuth, 마이크로서비스 간 인증전통적인 웹 애플리케이션의 로그인 세션 관리
무결성 검증토큰 자체에서 검증 가능 (서명 포함)서버에서 세션 데이터와 비교하여 검증

처음에 JWT를 사용한 이유

JWT는 서버에서 발행 후 클라이언트 측에 저장되기 때문에 서버는 별도 저장소를 관리하지 않아도 된다. 서버에서는 토큰 검증만 하면 되기에 단순하게 구현할 수 있어서 선택했다.

세션으로 변경한 이유

보안 검수 담당자와 이야기한 내용 중 일부를 발췌했다.

JWT 컨셉 자체가 서버 측에서 제어하고 있지 않는 것을 알고 있습니다. 권장 형태는 Redis나 DB 도입해서 인증값이 탈취되었을 때 파기 처리할 수 있는 방안이 필요합니다.

세션은 세션 ID가 탈취됐을 때 저장소에서 해당 세션 ID에 해당하는 데이터를 삭제하는 방식으로 무효 처리할 수 있다. 하지만, JWT는 데이터 저장 주체가 클라이언트 측에 저장되기 때문에 무효 처리하기가 어렵다.

세션 방식의 API Sequence (with SSO)

Session API Sequence

  • 2-1
    OAuth 2.0의 Authorization Code Grant 인증 방식은 Redirection 기반으로 진행되므로 세션이 만료된 경우 Authorization Server가 제공하는 로그인 페이지로 Redirection 한다.
  • 2-2
    인증 코드는 Authorization Server에서 발급하는 액세스 토큰을 가져오기 위한 코드다.
  • 2-3~4
    Authorization Server에서 자체 관리하는 세션을 체크한다. 세션이 유효할 경우 로그인 과정이 생략되고 바로 인증 코드를 발급한다.
  • 2-5~11
    Browser는 Session ID를 발급받기 위해 전달 받은 인증 코드를 Web Server에 전달한다. Web Server는 전달받은 인증 코드를 기반으로 Authorization Server로부터 액세스 토큰을 발급받은 후 해당 토큰을 사용하여 사내 프로필 정보를 조회한다. 임의의 Session ID를 Key, 사내 프로필 정보를 Value로 Redis에 저장한다. Redis 저장 시 데이터의 TTL을 요구사항에 맞게 설정한다.

구현

서버 미들웨어를 통해 인증을 체크한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default defineEventHandler(async (event) => {
  const { pathname: apiPath } = getRequestURL(event);
  const isProxyApi = PROXY_APIS.some((path) => apiPath.startsWith(path));

  if (isProxyApi) {
    // 인증(Authentication) 체크
    const sessionId = getCookie(event, 'session_id') || null;
    const sessionData = await verifySession(sessionId);
    if (!sessionData) {
      // 인증이 유효하지 않을 때 요청을 다음 미들웨어로 전달하지 않고 응답
      event.node.res.statusCode = 401;
      event.node.res.end();
      return;
    }

    // 중략...

  // 모두 통과되면 다음 미들웨어로 요청 전달
  }
});