컴퓨터 과학(CS)/네트워크

쿠키(cookie), 세션(session)

Bentist 2022. 2. 12. 17:18

쿠키 등장 배경

HTTP 프로토콜에는 비연결성(Connectionless)과 무상태성(Stateless)라는 특징 때문에 사용자의 요청에 대한 응답이 끝나면 연결 상태를 해제하고, 상태 정보를 저장하지 않기 때문에 서버의 자원을 크게 절약할 수 있었다. 그러나 이로 인해 사용자를 식별할 수 없어서 같은 사용자가 요청을 여러번 하더라도 매번 새로운 사용자로 인식하는 단점이 있다.

예를 들어 장바구니에 물품을 담은 경우 새로고침 시 장바구니가 초기화 되어 사용자가 불편을 겪게 된다. 마찬가지로 페이지를 이동할 때마다 로그인을 계속 해야 하는 불편함이 생기게 된다.

 

이 문제를 해결하기 위한 두 가지 방법이 있다.

1) 사용자 정보를 담은 임시 파일을 클라이언트에 저장하여, 서버는 이 파일로 클라이언트를 식별: 쿠키

2) 사용자 정보를 서버에 저장하고, 임의의 사용자 식별 ID를 만들어 클라이언트를 식별: 세션 (쿠키로 구현)

 

즉, 쿠키와 세션을 통해 서버와 클라이언트의 연결 상태가 유지되는 것처럼 만드는 방법이다.

 

사용자가 웹 서버에 요청을 하면, 웹 서버가 사용자 정보를 HTTP 응답 헤더의 Set-Cookie 속성에 넣어서 응답한다. 그러면 웹 브라우저는 웹 브라우저 내부의 쿠키 저장소에 서버가 보낸 사용자 정보를 Cookie로 저장한다. 이후 사용자는 서버에 다시 요청을 보낼 때 웹 브라우저의 쿠키 저장소에서 해당 웹 사이트의 사용자 정보를 조회하여 요청 헤더에 쿠키 정보를 자동으로 포함해서 서버로 전달하게 된다. 이로써 서버는 쿠키로 사용자를 식별하여 로그인을 유지하고 사용자별로 맞춤형 정보를 제공할 수 있게 된다.

쿠키

위키피디아

사용자가 웹 사이트를 방문할 경우 웹 서버가 보낸 사용자의 정보 파일을 일컫는다. HTTP 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. 쿠키는 임의의 작은 데이터로 웹 브라우저에 의해 사용자 컴퓨터에 저장된다.

출처: 위키피디아

HTTP 헤더의 쿠키 속성

Set-Cookie: 웹 서버에서 클라이언트로 쿠키 전달(응답)

Cookie: 서버에서 받은 쿠키를 클라이언트가 저장하고, HTTP 요청시 서버로 쿠키를 전달 

 

서버Set-Cookie 헤더로 클라이언트에게 쿠키를 저장하라고 응답

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: user=bentist

[body content]

웹 브라우저서버로 전송되는 요청에 쿠키를 자동으로 포함하여 전달

GET /cafe HTTP/1.1
Host: www.naver.com
Cookie: user=bentist

쿠키 생명 주기

Set-Cookie: expires=Sat, 26-Dec-2021 GMT ...  만료 날짜를 지정하여 쿠키 삭제

Set-Cookie: max-age=3600 (3600초) ... 초 단위로 지정하여 쿠키 삭제

쿠키 종류

영속 쿠키: 웹 브라우저가 종료되어도 지정한 날짜나 시간이 되어야 삭제된다.

세션 쿠키: expires 또는 max-age를 명시하지 않고 브라우저가 종료되면 삭제된다.

* 세션 ID를 담고 있는 쿠키와는 다른 의미이다. 세션 쿠키의 의미는 세션 ID의 포함 유무에 따른 것이 아니라 쿠키의 존속 여부다.  

 

세션: 서버에 사용자 인증 정보를 임시 저장하여 인증 상태를 유지하는 시간대 

일반적으로 세션은 컴퓨터 시스템의 관리자(OS 또는 서버)가 자신의 자산을 이용하는 것이 허락된 사용자를 식별할 일정한 기간을 가리키는 것이다.

위에서 설명한 쿠키를 이용해서 사용자의 아이디와 비밀번호를 쿠키에 저장한다고 가정해보자. 그러나 쿠키를 통해 아이디와 비밀번호를 주고 받으면 개인 컴퓨터가 아닌 경우 누구나 그 사용자의 개인 정보를 확인할 수 있고, 조작 될 수 있는 보안상 매우 큰 문제를 야기할 수 있다.

그래서 비밀번호 등의 민감한 인증 정보는 쿠키에 저장하지 않고 사용자의 식별자 Session Id를 생성해 서버에 저장한다. 서버에는 인증 정보와 더불어 이 세션 ID에 해당하는 로그인 상태, 마지막 로그인 시간, 닉네임, 만료기한 등의 정보를 저장한다. 보안상 서버는 사용자의 개인 컴퓨터보다는 훨씬 안전하다.

출처: 김영한님 스프링 MVC 2편, 인프런 강의

세션의 동작 방법: 세션 ID를 쿠키에 담아서 전달

    1. 클라이언트가 서버에 처음으로 로그인 Request를 보낸다. (첫 요청이면 세션 ID가 존재하지 않는다.)
    2. 서버에서는 해당 사용자의 세션 ID값이 없는 것을 확인하고 새로 세션 ID 생성하여 세션 저장소에 보관한다.
    3. Session ID는 브라우저 단위로 생성되어 저장된다.
    4. 서버는 클라이언트에 쿠키를 보낼 때 세션 ID를 포함하여 전달한다.
    5. 클라이언트는 쿠키저장소에 mySessionId 쿠키를 보관한다.
    6. 이후 클라이언트는 사용자의 mySessionId 쿠키를 요청과 함께 전달한다.
    7. 서버는 클라이언트에게 쿠키 값을 매번 받아서 내부의 세션 저장소에서 세션 ID 조회하여 사용자를 식별한다.

 

세션 쿠키의 이중적 의미

기본적으로 세션 쿠키는 브라우저가 종료되면 삭제되는 쿠키를 의미하고, 세션 ID를 저장하고 있는 쿠키를 세션 쿠키라고도 부른다. 만약 브라우저를 닫고 다시 열었을 때에도 로그인 상태를 유지하고 싶으면 영속 쿠키에 세션 ID를 저장해야 할 것이고, 아니라면 세션 쿠키에 세션 ID를 담으면 된다.

 

쿠키와 세션 삭제

클라이언트 입장에서는 웹 브라우저를 종료하면 쿠키가 삭제(세션 쿠키)되거나 만료 날짜가 되면 쿠키가 삭제(영속 쿠키)되지만, 서버 입장에서는 해당 사용자가 웹 브라우저를 종료했는지 아닌지를 인식할 수 없어서 세션 데이터를 언제 삭제해야 하는지 판단하기가 어렵다. 

 

이 경우 남아있는 세션을 무한정 보관하면 다음과 같은 문제가 발생할 수 있다.

  • 세션과 관련된 쿠키(SessionId)를 탈취 당했을 경우 오랜 시간이 지나도 해당 쿠키로 악의적인 요청을 할 수 있다.
  • 세션은 기본적으로 메모리에 생성된다. 메모리의 크기가 무한하지 않기 때문에 꼭 필요한 경우만 생성해서 사용해야 한다. 10만명의 사용자가 로그인 하면 10만개의 세션이 생성되는 것이다.

서버에서의 세션 정보 삭제

1) 사용자가 로그아웃을 하면 세션 정보는 내부 로직에 의해 보통 자동으로 삭제된다.

2) 서버에서 세션의 종료 시점을 지정한다. 예를 들어 세션의 종료 시점을 세션 생성 시점으로부터 30분으로 잡으면 30분마다 계속 로그인해야 하는 번거로움이 발생하므로, 사용자가 서버에 마지막으로 요청한 시간을 기준으로부터 30분 후에 세션을 종료하는 방법이다.


파이썬 실습

참고: https://docs.python-requests.org/en/latest/

 

Requests: HTTP for Humans™ — Requests 2.27.1 documentation

Requests: HTTP for Humans™ Release v2.27.1. (Installation) Requests is an elegant and simple HTTP library for Python, built for human beings. Behold, the power of Requests: >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.

docs.python-requests.org

1) Cookie

import requests

url = 'http://www.naver.com'
r = requests.get(url)

r.cookies

# 결과
>>> <RequestsCookieJar[Cookie(version=0, name='PM_CK_loc', 
value='6605d1069b7f9a20b6bc77ac81ca6fdb083119cbdc8b7505a8a42ea93d1df6dc', 
port=None, port_specified=False, domain='www.naver.com', domain_specified=False, 
domain_initial_dot=False, path='/', path_specified=True, secure=False, 
expires=1644894131, discard=False, comment=None, comment_url=None, 
rest={'HttpOnly': None}, rfc2109=False)]>

 

2) requests.Session(): 커넥션 유지

서버쪽 세션을 구현한다는 의미가 아닌(서버 쪽 세션을 당연히 클라이언트에서 건드릴 수도 없음), 동일한 호스트의 요청에 대해 쿠키 상태를 유지하는 역할

requests.Session 객체는 동일한 세션 인스턴스에서 생성된 모든 요청 간의 쿠키를 유지하고, urlib3의 연결 풀링 방식을 사용한다. 따라서 동일한 호스트에서 여러 요청을 하는 경우 TCP Connection이 재사용되므로 성능이 크게 향상될 수 있다. Session 객체는 Connection pool 방식을 사용한다고 했는데 Connection pool 방식은 무엇인가?  

클라이언트의 요청마다 3-way handshake를 거쳐 매번 커넥션을 형성하면 서버에 많은 비용이 발생한다. 그래서 미리 정해진 개수의 Connection을 생성해 Pool에 보관하고 보관된 Connection을 재사용하면 더 빠르고 효율적으로 통신을 할 수 있게 된다.

import requests

s = requests.Session()

url = 'http://www.naver.com'
session_con = s.get(url)

🔎 Session() 객체를 사용하면 정말 빠른가?

주피터 노트북을 통해 테스트해보겠다. 네이버의 메인 페이지를 100번 요청하여 수신한 시간을 측정하고자 한다.

 

1. 일반 요청

import requests
import time

start_time = time.time()

for i in range(100):
    url = 'https://docs.python-requests.org/en/latest/user/advanced/#session-objects'
    r = requests.get(url)
    print(f"Read {len(r.content)} from {url}")
    
duration = time.time() - start_time
print(f"{duration} seconds")

대략 23초 소요

 

2. Session 인스턴스 생성하여 요청

import requests
import time

s = requests.Session()
start_time = time.time()

for i in range(100):
    url = 'https://docs.python-requests.org/en/latest/user/advanced/#session-objects'
    session=s.get(url)
    print(f"Read {len(session.content)} from {url}")
    
duration = time.time() - start_time
print(f"{duration} seconds")

대략 12초 소요

 

결과적으로 매번 커넥션을 형성하는 것보다 Connection을 재사용하는 Session 객체가 훨씬 더 빠르다.