사전 세션 관리
개념

세션 관리

gabury1고친 사람 github-actions[bot]

세션 관리는 로그인한 사용자를 다음 요청에서도 알아보게 해 줍니다. 서버는 로그인 때 번호표를 하나 건넵니다. 브라우저는 그 뒤 요청마다 이 번호표를 내밉니다. 이 번호표를 언제 거두고 어떻게 도둑맞지 않게 지킬지가 이 일의 본체입니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 로그인을 한 번만 하게 해 주는 장치입니다. 쇼핑몰에 로그인한 뒤 장바구니로 넘어가도 비밀번호를 다시 묻지 않는 것이 이 덕분입니다.

왜 이렇게 하나 — 웹 서버는 방금 들어온 요청이 누구에게서 왔는지 기억하지 못합니다. 요청마다 비밀번호를 보내게 하면 번거롭습니다. 비밀번호가 그만큼 자주 오가는 것도 위험합니다.

어떻게 도나

  1. 로그인이 맞으면 서버가 무작위 번호표를 만들어 누구 것인지 적어 둡니다
  2. 번호표를 쿠키(서버가 브라우저에 맡겨 두는 작은 값)에 담아 브라우저에 줍니다
  3. 브라우저가 요청마다 쿠키를 붙이면 서버가 번호표로 사용자를 찾습니다

대가 — 번호표만 훔치면 비밀번호 없이도 그 사람 행세를 할 수 있습니다. 그래서 지킬 것이 셋입니다. 번호표는 훔치기 어렵게 담습니다. 오래 살려 두지 않습니다. 로그아웃 때는 서버에서 지웁니다.

상세

호텔에 묵으면 체크인할 때 프런트에 신분증을 한 번 보여 줍니다. 그러면 카드키를 받습니다. 그 뒤로는 방에 들어갈 때마다 신분증 대신 카드키를 댑니다. 체크아웃하고 나면 같은 카드를 대도 문이 열리지 않습니다.

브라우저와 서버는 HTTP(HyperText Transfer Protocol)라는 규칙으로 요청과 응답을 주고받습니다. 이 규칙은 무상태입니다. 서버는 요청 하나를 처리하고 나면 그 요청을 잊습니다. 다음 요청이 같은 사람에게서 왔는지는 요청만 보고 알 수 없습니다.

이 성질 때문에 인증만으로는 모자랍니다. 인증은 「당신이 누구인가」를 한 번 확인하는 일입니다. 쇼핑몰에 로그인한 뒤 장바구니 화면을 열면 그 요청은 서버에게 새 요청입니다. 서버가 로그인을 기억하지 못하면 장바구니 화면에서 비밀번호를 또 물어야 합니다.

요청마다 비밀번호를 함께 보내게 하는 방법도 있습니다. 사용자는 번거롭습니다. 비밀번호도 네트워크를 그만큼 자주 오갑니다. 오가는 횟수가 늘수록 비밀번호가 새어 나갈 기회도 늘어납니다.

세션 관리는 이 틈을 메웁니다. 로그인이 맞으면 서버는 그 사용자 몫의 기록을 하나 만듭니다. 이 기록을 세션이라고 부릅니다. 세션 관리는 세션을 만들고, 요청마다 찾아 주고, 때가 되면 끝내는 일 전체를 말합니다.

세션을 이루는 세 부품

세션 하나는 세 부품으로 이루어집니다. 서버 쪽에 둘, 브라우저 쪽에 하나가 있습니다.

첫째는 세션 식별자입니다. 세션 하나를 가리키는 무작위 문자열입니다. 흔히 세션 ID(identifier, 식별자)라고도 부릅니다. 앞 비유의 카드키가 이것입니다. 개요에서 번호표라고 부른 것도 이것입니다.

둘째는 세션 저장소입니다. 서버가 식별자마다 「이 세션은 누구이고 언제 만들었나」를 적어 두는 곳입니다. 서버 메모리일 수도 있고 데이터베이스일 수도 있습니다. 장바구니처럼 그 사용자가 쓰던 값을 함께 적어 두기도 합니다.

셋째는 브라우저 쪽에서 식별자를 간직하는 그릇입니다. 웹에서는 대개 쿠키를 씁니다. 쿠키는 서버가 브라우저에 맡겨 두는 작은 이름과 값의 쌍입니다. 브라우저는 같은 사이트로 요청을 보낼 때마다 이 값을 알아서 붙입니다.

한 번의 로그인이 지나가는 순서

로그인 한 번과 그다음 요청 한 번 사이에 브라우저, 서버, 세션 저장소 셋이 말을 주고받습니다. 아래 그림이 그 순서입니다.

그림에 나오는 헤더는 요청과 응답 머리에 붙는 이름과 값의 줄입니다. 본문과 따로 붙어서 그 요청이나 응답에 대한 정보를 실어 나릅니다.

쿠키도 헤더에 실려 오갑니다. 서버는 쿠키를 맡길 때 응답에 Set-Cookie 헤더를 싣습니다. 브라우저는 그 뒤 요청마다 Cookie 헤더에 그 값을 담아 보냅니다.

sequenceDiagram
    participant 브라우저
    participant 서버
    participant 저장소 as 세션 저장소
    브라우저->>서버: 아이디와 비밀번호
    Note over 서버: 비밀번호를 확인한다
    서버->>저장소: 새 식별자로 세션을 적는다
    서버-->>브라우저: Set-Cookie 에 식별자
    브라우저->>서버: Cookie 에 식별자를 붙인 요청
    서버->>저장소: 식별자로 세션을 찾는다
    저장소-->>서버: 누구의 세션인지
    서버-->>브라우저: 그 사용자의 화면

그림의 앞 세 화살표가 로그인이고 뒤 네 화살표가 그다음 요청입니다. 로그인 뒤로는 비밀번호가 다시 오가지 않습니다. 서버는 식별자 하나로 저장소를 찾아 사용자를 알아봅니다.

식별자가 갖춰야 할 성질

식별자만 알면 그 사람 행세를 할 수 있습니다. 그러니 식별자는 비밀번호만큼 귀하게 다뤄야 합니다. 만들 때 지킬 성질이 셋입니다.

첫째, 추측할 수 없어야 합니다. 1, 2, 3 처럼 차례로 매기면 남의 번호를 쉽게 짐작합니다. 그래서 식별자는 암호학적 난수 생성기로 만듭니다. 앞에 나온 값들을 다 알아도 다음 값을 짐작할 수 없게 설계된 난수 생성기입니다.

둘째, 충분히 길어야 합니다. 짧으면 공격자가 가능한 값을 하나씩 다 넣어 볼 수 있습니다. 이렇게 모든 값을 차례로 시도하는 공격을 무차별 대입 공격이라고 합니다.

셋째, 아무 뜻도 담지 않아야 합니다. 사용자 번호나 이메일을 식별자에 넣으면 그 값만 보고도 정보가 샙니다. 식별자는 저장소를 찾는 열쇠일 뿐입니다. 사용자 정보는 저장소에 둡니다.

식별자를 담는 쿠키의 속성

식별자를 담은 쿠키에는 속성을 붙입니다. 속성은 브라우저가 그 쿠키를 어떻게 다룰지 정하는 꼬리표입니다. 식별자를 지키는 일은 대부분 이 꼬리표 넷으로 합니다.

아래는 서버가 로그인 응답에 싣는 헤더 한 줄입니다. sid 는 애플리케이션이 정한 쿠키 이름입니다. 값은 예로 든 짧은 문자열입니다. 실제 식별자는 이보다 훨씬 깁니다.

http
Set-Cookie: sid=Qx7f2Lw9; HttpOnly; Secure; SameSite=Lax

이름과 값 뒤에 세미콜론으로 이어 붙은 낱말들이 속성입니다. 표에 나오는 HTTPS(HyperText Transfer Protocol Secure)는 HTTP 를 암호화해서 주고받는 방식입니다.

속성 브라우저가 하는 일 막는 것
HttpOnly 페이지의 자바스크립트가 이 쿠키를 읽지 못하게 한다 스크립트로 식별자를 빼 가기
Secure HTTPS 연결에서만 이 쿠키를 보낸다 암호화 안 된 연결에서 엿보기
SameSite 다른 사이트에서 시작된 요청에는 쿠키를 안 붙이거나 덜 붙인다 남의 사이트가 내 이름으로 요청 보내기
Expires · Max-Age 쿠키를 언제까지 간직할지 정한다 쿠키가 기기에 오래 남기

넷 모두 서버가 헤더에 적기만 하면 브라우저가 지켜 줍니다. 「막는 것」 칸의 앞 세 줄은 아래 「세션을 노리는 공격」 소절의 공격들과 짝을 이룹니다. Expires · Max-Age 줄은 「세션이 끝나는 때」 소절과 이어집니다.

SameSite 는 값을 셋 가운데 고릅니다. Strict 는 다른 사이트에서 시작된 요청에 쿠키를 아예 안 붙입니다. Lax 는 링크를 눌러 넘어오는 평범한 이동에만 붙입니다. None 은 언제나 붙입니다.

Expires 와 Max-Age 를 둘 다 안 적은 쿠키는 대개 브라우저를 닫을 때 버려집니다. 언제 버릴지는 브라우저가 정합니다. 브라우저를 닫았다 열어도 앞 화면을 되살리는 브라우저에서는 이런 쿠키가 살아남기도 합니다.

세션이 끝나는 때

세션이 오래 살수록 식별자를 훔친 사람이 그것을 쓸 수 있는 시간도 길어집니다. 세션은 때가 되면 끝내야 합니다. 끝내는 길은 넷입니다.

유휴 타임아웃은 요청이 없는 시간이 정해 둔 길이를 넘으면 세션을 끝냅니다. 요청이 올 때마다 시계를 처음부터 다시 잽니다. 컴퓨터를 켜 둔 채 사용자가 떠난 사이에 남이 그 화면을 쓰는 일을 줄입니다.

절대 타임아웃은 요청이 계속 와도 세션을 만든 지 정해 둔 시간이 지나면 끝냅니다. 식별자를 훔친 사람이 요청을 꾸준히 보내 세션을 끝없이 늘리는 일을 막습니다.

로그아웃은 사용자가 스스로 세션을 끝내는 것입니다. 이때 브라우저의 쿠키만 지우면 모자랍니다. 그 식별자를 미리 베껴 둔 사람은 여전히 들어올 수 있습니다. 그래서 서버가 저장소에서 세션을 지워야 합니다.

서버가 먼저 끝내야 할 때도 있습니다. 비밀번호를 바꿨거나 계정이 털린 낌새가 보이면 그 사용자의 세션을 모두 지웁니다. 다른 기기에서 열려 있던 로그인까지 함께 끊깁니다.

세션이 살아 있어도 다시 묻는 경우가 있습니다. 비밀번호 변경이나 송금처럼 되돌리기 어려운 작업 앞에서 비밀번호를 한 번 더 받는 것입니다. 이것을 재인증이라고 합니다. 훔친 식별자만으로는 그런 작업까지 하지 못하게 막습니다.

세션을 노리는 공격

대표 공격 셋은 모두 비밀번호 대신 세션을 노립니다. 하나는 식별자를 훔칩니다. 하나는 식별자를 미리 심어 둡니다. 나머지 하나는 브라우저가 알아서 붙이는 쿠키에 얹혀 갑니다.

세션 하이재킹은 남의 세션 식별자를 손에 넣어 그 사람 행세를 하는 공격입니다. 훔치는 길은 둘이 대표적입니다. 하나는 암호화 안 된 연결을 엿보는 것입니다. Secure 속성과 HTTPS 가 이 길을 막습니다.

다른 길은 교차 사이트 스크립팅입니다. XSS(Cross-Site Scripting)라고 줄여 부릅니다. 공격자가 남의 페이지에 자기 스크립트를 끼워 넣는 공격입니다. 그 스크립트는 페이지를 연 사람의 브라우저에서 돕니다. 스크립트가 쿠키를 읽어 공격자에게 보내면 식별자가 넘어갑니다. HttpOnly 속성은 스크립트가 쿠키를 못 읽게 해서 이 길을 막습니다.

세션 고정은 방향이 거꾸로입니다. 공격자가 자기가 아는 식별자를 피해자 브라우저에 미리 심어 둡니다. 피해자가 그 식별자를 단 채 로그인하면 공격자도 같은 식별자로 로그인된 세션을 씁니다.

세션 고정은 로그인에 성공하는 순간 식별자를 새로 발급해서 막습니다. 로그인 전에 쓰던 식별자는 버립니다. 그러면 공격자가 심어 둔 식별자는 로그인과 함께 쓸모가 없어집니다.

크로스 사이트 요청 위조는 CSRF(Cross-Site Request Forgery)라고 줄여 부릅니다. 브라우저는 요청이 어느 페이지에서 시작됐든 그 사이트의 쿠키를 알아서 붙입니다. 공격자는 이 성질을 씁니다. 피해자가 공격자의 페이지를 열면 그 페이지가 은행 사이트로 송금 요청을 보냅니다.

이 요청에는 피해자의 세션 쿠키가 붙어 있습니다. 은행은 그 쿠키를 보고 피해자가 보낸 요청으로 압니다. SameSite 속성은 다른 사이트에서 시작된 요청에 쿠키를 안 붙여 이 공격을 막습니다.

CSRF 토큰은 서버가 자기 폼마다 심어 두는 무작위 값입니다. 요청에 그 값이 돌아오는지 확인해서 남의 사이트가 보낸 요청을 가립니다. 공격자의 페이지는 이 값을 알 수 없습니다. 브라우저가 한 사이트의 페이지에서 다른 사이트 페이지의 내용을 읽지 못하게 막기 때문입니다.

서버가 여러 대일 때

서버를 여러 대 띄우면 세션을 어디에 두느냐가 문제가 됩니다. 요청을 서버 여러 대에 나눠 주는 장치를 로드 밸런서라고 합니다.

세션을 서버 메모리에 두면 문제가 생깁니다. 로그인은 1번 서버가 받았습니다. 다음 요청이 2번 서버로 가면 2번 서버는 그 식별자를 모릅니다. 사용자는 로그인이 풀린 것처럼 보게 됩니다.

첫째 해법은 모든 서버가 같은 저장소를 보게 하는 것입니다. 세션을 데이터베이스나 키-값 저장소에 두면 어느 서버로 가도 같은 세션을 찾습니다. 키-값 저장소는 키 하나로 값 하나를 찾아 주는 단순한 저장소입니다. 세션 식별자를 키로 쓰기 알맞습니다. 대가는 요청마다 저장소를 한 번 더 다녀오는 것입니다.

둘째 해법은 스티키 세션입니다. 로드 밸런서가 같은 사용자의 요청을 늘 같은 서버로 보냅니다. 저장소를 바꿀 필요는 없습니다. 대신 그 서버가 죽으면 그 서버에 있던 세션이 모두 사라집니다. 사용자가 몰린 서버만 바빠지기도 합니다.

서버에 두는 세션과 토큰에 담는 세션

세션 정보를 어디에 두느냐에 따라 방식이 둘로 갈립니다. 지금까지 본 것은 서버가 저장소에 기록을 두는 방식이었습니다.

다른 방식은 사용자 정보를 토큰 안에 담아 브라우저에 맡기는 것입니다. 토큰은 「이 사용자는 누구이고 언제까지 유효한가」를 적은 문자열입니다. 요청이 오면 서버는 저장소를 찾는 대신 토큰에 적힌 내용을 읽습니다.

이대로면 사용자가 토큰 내용을 고쳐 남 행세를 할 수 있습니다. 이것을 막는 것이 서명입니다. 서명은 토큰 내용과 서버만 아는 열쇠로 계산해 붙인 값입니다. 열쇠가 없는 사람은 내용을 고친 뒤 맞는 서명을 다시 만들 수 없습니다.

이런 토큰의 대표 형식이 JWT(JSON Web Token)입니다. JSON(JavaScript Object Notation)은 이름과 값의 쌍을 글자로 적는 형식입니다. 아래 표는 두 방식을 네 가지 물음으로 견줍니다.

서버에 두는 세션 토큰에 담는 세션
브라우저가 가진 것 뜻 없는 식별자 사용자 정보와 서명
요청마다 서버가 하는 일 저장소에서 식별자를 찾는다 서명을 확인한다
서버가 여러 대일 때 모두가 같은 저장소를 봐야 한다 열쇠만 나눠 가지면 된다
지금 당장 끊기 저장소에서 지우면 바로 끝난다 만료 시각 전에는 끊기 어렵다

마지막 줄이 둘을 가르는 가장 큰 차이입니다. 토큰 방식은 서버에 기록이 없습니다. 「이 토큰은 이제 무효」라고 적어 둘 곳도 없습니다.

토큰 방식은 토큰의 수명을 짧게 잡는 일이 많습니다. 훔친 토큰이라도 금방 쓸모없어지게 하려는 것입니다. 수명이 짧으면 사용자가 자주 다시 로그인해야 하는 문제가 남습니다.

리프레시 토큰이 이 문제를 덜어 줍니다. 짧은 토큰이 만료되면 브라우저는 리프레시 토큰을 내밀어 새 토큰을 받습니다. 서버는 새 토큰을 내줄 때마다 리프레시 토큰을 확인합니다. 끊을 사용자에게는 새 토큰을 안 내주면 남은 짧은 토큰이 만료되는 대로 접근이 끝납니다.

무효가 된 토큰 목록을 서버에 두는 방법도 있습니다. 그러면 만료를 기다리지 않고 당장 끊을 수 있습니다. 다만 요청마다 그 목록을 찾게 되어 서버에 두는 세션과 비슷해집니다.

고르는 기준도 이 차이에서 나옵니다. 로그아웃이나 계정 정지를 바로 반영해야 하면 서버에 두는 세션이 맞습니다. 서버가 여럿이라 공유 저장소를 두기 부담스러우면 토큰 방식이 가볍습니다.

백엔드 코드에서 만나는 모습

백엔드 개발자가 세션 관리를 직접 짤 일은 드뭅니다. 대부분의 웹 프레임워크가 이미 갖추고 있습니다.

프레임워크는 세션 관리를 미들웨어로 줍니다. 미들웨어는 요청이 핸들러에 닿기 전과 응답이 나간 뒤에 공통 일을 처리하는 코드입니다. 세션 미들웨어는 요청에서 쿠키를 읽어 세션을 찾아 두고, 응답에 쿠키를 실어 줍니다.

개발자가 하는 일은 설정을 고르는 것입니다. 무엇을 골라야 하는지는 앞 소절들이 다룬 것과 같습니다.

고를 것 이어지는 소절
세션 저장소를 메모리에 둘지 공유 저장소에 둘지 서버가 여러 대일 때
쿠키에 붙일 속성 식별자를 담는 쿠키의 속성
유휴 타임아웃과 절대 타임아웃의 길이 세션이 끝나는 때
로그인 때 식별자를 새로 발급할지 세션을 노리는 공격
로그아웃 때 서버 쪽 세션을 지울지 세션이 끝나는 때

프레임워크마다 기본값이 다릅니다. 로그인 때 식별자를 새로 발급하는 일은 개발자가 직접 불러 줘야 하는 프레임워크도 있습니다. 그래서 표의 다섯 줄은 쓰는 프레임워크에서 기본값이 무엇인지 하나씩 확인할 대상입니다.

이름이 같은 다른 세션 관리

「세션 관리」라는 이름은 웹 로그인 밖에서도 쓰입니다. 이 문서의 나머지는 모두 웹 로그인의 세션 관리를 다뤘습니다.

데이터베이스에서는 클라이언트가 접속해 있는 동안의 상태를 세션이라고 부릅니다. 이 접속을 열고 닫는 일을 세션 관리라고 부르기도 합니다.

TLS(Transport Layer Security)는 연결을 암호화하는 규칙입니다. 연결을 맺을 때 양쪽은 어떤 암호와 열쇠를 쓸지 먼저 정합니다. 이렇게 정한 결과를 다음 연결에서 다시 쓰는 일을 세션 재개라고 합니다. 정하는 과정을 건너뛰어 연결이 빨라집니다.

세 경우 모두 「흩어진 요청이나 연결을 한동안 하나로 묶는다」는 뼈대는 같습니다. 무엇을 묶는지가 다릅니다. 「세션」이라는 낱말이 맥락마다 무엇을 가리키는지는 세션 문서가 갈라 둡니다.

관련 항목

세션 관리 앞뒤에 서는 접근 통제 단계

인증 · 로그인 · 다중 인증 · 재인증 · 인증과 인가 · 인가 · 접근 제어

세션을 이루는 구성 요소

세션 · 세션 식별자 · 세션 저장소 · 쿠키 · Set-Cookie · 세션 쿠키

세션 식별자를 지키는 쿠키 속성

HttpOnly · Secure 쿠키 · SameSite · Expires · Max-Age

세션을 노리는 공격

세션 하이재킹 · 세션 고정 · 교차 사이트 스크립팅 · 크로스 사이트 요청 위조 · 무차별 대입 공격 · 중간자 공격

세션 공격을 막는 수단

HTTPS · TLS · CSRF 토큰 · 암호학적 난수 생성기 · 콘텐츠 보안 정책 · 세션 재발급

세션을 끝내는 방식

유휴 타임아웃 · 절대 타임아웃 · 로그아웃 · 세션 무효화 · 만료 · 타임아웃

서버에 두는 세션을 대신하는 토큰 방식

토큰 · JWT · 액세스 토큰 · 리프레시 토큰 · 토큰 폐기 · OAuth 2.0

여러 서버가 세션을 나눠 쓰는 구성

로드 밸런서 · 스티키 세션 · 키-값 저장소 · Redis · 분산 캐시 · 수평 확장

세션 관리가 기대는 웹의 성질

HTTP · 무상태 · HTTP 헤더 · 동일 출처 정책 · 미들웨어

세션이라는 이름이 겹치는 용어

세션 · 데이터베이스 세션 · TLS 세션 재개 · 연결

세션 관리가 속하는 상위 분야

보안 엔지니어링 · 웹 보안 · 계정 보안 · 인증 보증 수준

다른 이름: session management