쿠키, 세션, 토큰, JWT

김민재·2025년 7월 7일

CS

목록 보기
7/12

사용자 인증에 사용되는 쿠키, 세션, 토큰, JWT에 대해 정리한 글이다.


쿠키에 대해 설명하기 전에, 한 가지 알고 넘어가야 할 것이 있다.

여러 블로그 포스팅을 통해 관련 개념들을 공부했지만, 대부분의 글에서 쿠키를 하나의 인증 방식으로 설명하고 있었다.

하지만 내가 조사하면서 느낀 점은, 쿠키는 독립적인 인증 방식이라기보다는, 인증에 사용되는 정보를 저장하는 작은 데이터 저장소일 뿐이라는 것이다.

즉, 쿠키는 단순히 후술할 세션 ID나 토큰 등을 저장하고 전송하는 수단일 뿐, 인증 방식 그 자체로 보기에는 어렵다.

📌 쿠키

쿠키는 웹 서버가 생성하여 웹 브라우저로 전송하는 작은 정보 파일이다. 여기에는 사이트의 언어, 사용자 인증에 대한 정보 등 서버가 브라우저에 저장하고자 하는 정보들이 담겨 있다.

브라우저는 수신한 쿠키를 미리 정해진 기간 동안 또는 웹 사이트에서의 사용자 세션 기간 동안 저장하고, 향후 사용자가 웹 서버에 요청할 때 관련 쿠키를 첨부하게 된다.

브라우저는 위와 같이 http 요청 메시지의 Cookie 헤더에 데이터를 첨부한다.

서버에서는 위와 같이 http 응답 메시지의 Set-Cookie 헤더에 데이터를 첨부한다.

개발자 도구 > 애플리케이션 > 쿠키 에는 현재 사용 중인 사이트(또는 해당 도메인)에 대해 브라우저에 저장되어 있던 쿠키 정보가 표시된다.


만약 쿠키에 민감한 사용자 정보를 담아 인증을 하게 되면, 이 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재한다.

이를 방지하기 위해 세션 방식이 사용될 수 있다.

📌 세션 방식

세션은 비밀번호 등 클라이언트의 민감한 인증 정보를 브라우저가 아닌 서버 측에 저장하고 관리한다. 서버의 메모리에 저장하기도 하고, 서버의 로컬 파일이나 데이터베이스에 저장하기도 한다. 

핵심은 민감한 정보는 클라이언트에 보내지 않고 서버에서 모두 관리한다는 점이다.

🔎 세션 방식의 동작 과정

1. 사용자가 로그인을 요청한다.

2. 서버에서는 회원 DB에서 계정 정보를 읽어 사용자를 확인하고, 사용자의 고유한 Id를 확인하여 세션 저장소에 저장한후, 이와 연결된 세션Id를 발급한다.

✔️ 사용자 고유 ID (userId): 데이터베이스에 저장된 각 사용자를 식별하는 고유한 값
✔️ 세션 ID (sessionId): 서버가 생성하는 임의의 고유 값으로, 클라이언트(브라우저)와 세션 저장소를 연결하기 위한 식별자

3. 사용자는 서버에서 해당 세션Id를 받아 쿠키에 저장한 후, 인증이 필요한 요청마다 쿠키를 헤더에 실어 보낸다.

4. 서버는 쿠키를 받아 세션 저장소에서 대조 후 대응되는 정보를 가져온다.

5. 인증이 완료되면 서버는 사용자에 맞는 데이터를 보내준다.

🔎 세션 방식의 단점

  • 해커가 세션 ID 자체를 탈취하여 클라이언트인척 위장할 수 있다는 한계가 존재한다.
  • 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.

📌 토큰 방식

토큰 방식에서는 사용자 인증에 관한 정보가 서버가 아닌 클라이언트에 저장되기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있다.

토큰의 형식은 다음과 같이 랜덤한 문자열로 구성되어 있다. (JWT 형식)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJ1c2VySWQiOjEyMywiZW1haWwiOiJtYWlsQGV4YW1wbGUuY29tIn0
.D3kL8UoGCEknn0zpE9bCeGbbVtQn0V6C0soK0nJWehg

🔎 토큰 방식의 동작 과정

1. 사용자가 아이디와 비밀번호로 로그인을 한다.

2. 서버 측에서 사용자(클라이언트)에게 유일한 토큰을 발급한다.

3. 클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해 두고, 서버에 요청을 할 때마다 해당 토큰을 HTTP 요청 헤더에 포함시켜 전달한다.

4. 서버는 전달받은 토큰을 검증하고 요청에 응답한다. 토큰에는 요청한 사람의 정보가 담겨있기에 서버는 DB를 조회하지 않고 누가 요청하는지 알 수 있다.


토큰은 앱과 서버가 통신 및 인증할때 가장 많이 사용된다. 왜냐하면 웹에는 쿠키와 세션이 있지만 앱에서는 없기 때문이다.

🔎 토큰 방식의 단점

  • 쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질수 있다.
  • payload 자체는 암호화되지 않기 때문에 비밀번호와 같이 민감한 정보는 저장하지 못한다.
  • 토큰을 탈취당하면 대처하기 어렵다. (따라서 사용 기간 제한을 설정하는 식으로 극복한다)

📌 JWT 방식

JWT 방식은 인증에 필요한 정보들을 암호화시킨 JWT(Json Web Token)를 사용하여 인증을 하는 방식이다.

우선 JWT는 다음과 같이 세 부분으로 나뉜다.

각각의 구성 요소에 대해 알아보자.

1. 헤더(header)

헤더에는 JWT에서 사용할 타입과 해시 알고리즘의 종류가 담겨있으며, Base64url로 인코딩되어 있다.

{
  "alg": "HS256",
  "typ": "JWT"
}
  

✔️ alg: 암호화 알고리즘으로서, headerpayload, 그리고 서버가 가지고 있는 비밀 키를 이 알고리즘으로 암호화한 것이 signature가 된다.
✔️ typ: JWT로 고정된 값이다.

❓ Base64url이란

일반적인 Base64 인코딩은+, /, = 와 같은 문자를 사용한다.
그런데 이 문자들은 URL이나 파일 이름에서 특별한 의미를 가지거나
일부 환경에서 문제가 될 수 있다.

따라서 이들을 다른 적절한 문자로 바꾸어 URL에 사용하기 적합하도록 바꾼 것이 바로 Base64url이다.

2. 내용(payload)

payload 에는 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다. header와 마찬가지로 Base64url로 인코딩되어 있다.

{
	"sub": "1234567890" /* claim */
    "name": "John Doe" /* claim */
    "iat": 1516239022 /* claim */
}

토큰에서 key-value 형식으로 이루어진 한 쌍의 정보를 claim 이라고 한다. 이 정보들은 JWT를 디코딩함으로서 실제로 알 수 있다.

즉, payload는 서버와 클라이언트가 주고받는 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있는 섹션이다.

3. 서명(signature)

서명은 JWT의 headerpayload, 그리고 서버가 가지고 있는 비밀키를 header 에서 정의한 알고리즘으로 암호화한 값이다.

headerpayload의 경우 단순히 Base64url로 인코딩한 값이라 제 3자가 쉽게 해독하여 볼 수 있지만, 암호화 알고리즘은 한 방향으로만 계산이 되기 때문에 signature의 경우 디코딩 할 수 없다. 즉 서버의 비밀 키를 알 수 있는 방법이 없다.

❓ 그럼 JWT도 보안에 취약한 것 아닌가?

앞서 말했듯이, JWT의 payload는 단순히 Base64URL로 인코딩되어 있기 때문에 쉽게 디코딩할 수 있다. 즉, JWT의 목적은 정보 보호가 아니라 위조 방지에 있다.

예를 들어, 제3자가 토큰을 탈취한 뒤 payload를 수정하여 서버에 요청하더라도,
서버는 header, payload, 그리고 서버가 보관 중인 비밀 키(secret key)를 바탕으로 signature를 다시 계산한다.
이때 계산된 signature는 위조된 토큰의 signature와 일치하지 않기 때문에, 서버는 요청을 신뢰하지 않는다.

실제로 정보 보호와 암호화는 JWT 자체가 아닌, HTTPS와 같은 전송 계층 보안이 담당한다고 보는 것이 타당할 것 같다.

❓ 그래서 JWT는 어떻게 동작하는가?

JWT 동작 과정의 전반을 알기 위해서는, 우선 Access TokenRefresh Token에 대해 알아볼 필요가 있다.

🔎 Access Token과 Refresh Token

다만 이 JWT도 만약 토큰이 제 3자에게 탈취당한다면, 위에서 서술했다시피 headerpayload의 정보가 그대로 노출될 수 있다.

따라서 이를 최대한 방지하기 위해 현업에서는 유효기간이 정해져 있는 Access token과, 이 Access Token을 새로 발급받기 위한 Refresh token 두 가지를 함께 사용한다고 한다.

✔️ Access Token

  • 클라이언트가 갖고있는 실제 유저의 정보가 담긴 토큰으로, 클라이언트에서 요청이 오면 서버에서 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행한다.
  • 보통 짧은 수명 (5분~1시간)

✔️ Refresh Token

  • 새로운 Access Token을 발급해주기 위해 사용하는 토큰
  • 긴 수명 (며칠~몇 주)
  • Access Token을 재발급받는 데 쓰이므로, 탈취되면 해커는 사용자와 똑같이 로그인된 상태를 계속 유지할 수 있다. 완전히 로그인이 탈취되는것과 다름없다.
    -> 따라서 브라우저의 HttpOnly + Secure 쿠키에 저장하는 것이 안전하다.
  • 토큰은 보통 데이터베이스에 유저 정보와 같이 기록한다.

❓ HttpOnly, Secure 쿠키?

Refresh Token의 경우에는 HttpOnly와 Secure 쿠키에 저장하는 것이 안전하다고 했는데, 무엇인지 알아보자.

✔️ HttpOnly
HttpOnly 쿠키 속성을 사용하면 JavaScript를 통해 쿠키에 접근할 수 없게 되어, 악성 스크립트를 통해 쿠키 값에 접근하는 것을 막아준다.
HTTP Only Cookie 를 활성화 하기 위해서는 쿠키를 생성할 때, 가장 마지막에 HttpOnly 라는 접미사를 추가하면 된다.

✔️ Secure쿠키
HTTP Only Cookie를 사용하면 클라이언트에서 Javascript를 통한 쿠키 탈취문제를 예방할 수 있다.
하지만 Javascript가 아닌 네트워크를 직접 감청하여 쿠키를 가로챌 수도 있다.
그렇기 때문에 이러한 정보 유출들을 막기 위해, HTTPS 프로토콜을 사용하여 데이터를 암호화하여 서버에 넘겨주게 되면, 해커들이 쿠키를 탈취해도 암호화가 되어있어 정보를 알아낼 수 없다.
이를 위해 Secure 접미사를 사용해서 쿠키를 생성하게 되면 브라우저는 HTTPS 가 아닌 통신에서는 쿠키를 전송하지 않는다.

🔎 동작 과정


1. 사용자가 ID, PW를 입력하여 서버에 로그인 인증을 요청한다.

2. 서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad, Signature를 정의한다. Hedaer, PayLoad, Signature를 각각 Base64로 한 번 더 암호화하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.

3. 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다. (쿠키나 다른 곳에 저장할 수도 있음)API를 서버에 요청할때 Authorization header에 Access Token을 담아서 보낸다.

4. 서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하여 일치한다면 인증을 통과시켜주고 아니라면 통과시키지 않으면 된다. 인증이 통과되었으므로 페이로드에 들어있는 유저의 정보들을 select해서 클라이언트에 돌려준다.

5. 클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프래시 토큰을 이용해서 서버로부터 새로운 엑세스 토큰을 발급 받는다.


출처

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC
https://velog.io/@eeeve/HttpOnly%EC%99%80-Secure-Cookie

profile
넓이보다 깊이있게

0개의 댓글