웹서버는 어떻게 사용자를 인증할까? (cookie, session, token)

Lee Yechan·2023년 8월 6일
0

프로그래밍 지식들

목록 보기
10/10

이 글에서는...

이 글에서는 웹서버가 어떤 방식을 사용해 사용자를 인증하는지 정리해보려고 한다.


Auth

Auth는 보통 authentication(인증)과 authorization(인가)을 통칭하는 말로 사용되거나, authentication(인증)만을 가리키기도 한다.

authentication과 authorization 둘의 스펠링이 비슷하기도 하고, 단어가 사용되는 맥락이나 상황도 비슷하기 때문에 두 개념을 헷갈릴 수 있으나, 구분이 필요하다.

간단하게 뭉뚱그려 표현하면 authentication은 로그인, authorization은 액세스 제어라고 할 수 있는데, 이 글에서는 주로 authentication에 대해서 다루겠다.

Authentication

authentication은 사용자의 신원을 확인하는 과정이다.

예시)

  • 네이버에 로그인하기 위해 비밀번호로 인증한다.
  • 휴대전화의 잠금을 해제하기 위해 생체 정보로 인증한다.

Authorization

authentication은 사용자에게 권한을 부여하는 것으로, 시스템 리소스에 대한 접근 수준을 결정하는 데에 사용된다.

예시)

  • 네이버 카페에서, 카페 관리자는 멤버들을 추가, 추방할 권한을 가지지만 일반 사용자는 그렇지 못한다.
  • 구글 공유 드라이브에서, 드라이브 소유자가 지정한 특정 사용자는 공유 드라이브의 내용을 열람/수정/삭제/추가 가능하기도, 열람만 가능하기도, 열람이 불가능할 수도 있다.

인증, 어떻게 해야 할까?

어떻게 인증을 해야 할지, 예시를 들어 설명해보겠다.

castle

당신은 성의 게이트를 출입하는 사람들을 관리하는 문지기이다.

당신은 문지기로서, 성 안에 살고 있는 주민들만 성문 안으로 통과시켜야 한다(=인증, authentication).

하지만, 사람들이 너무 많기도 하고 헷갈려서 당신은 성문 안으로 들어오려는 사람의 얼굴만 보고서는 그 사람이 성의 주민인지, 아닌지 알 수 없다(=HTTP는 stateless한 특성을 가지기 때문).

그래서, 당신은 이 사람이 주민인지, 아닌지 판단할 방법을 찾아보려고 한다.


1. 주민 리스트 확인 (session)

당신이 첫 번째로 생각해낸 방법은, 성 안의 주민들의 리스트를 가지고 와서 그 안에 그 주민의 정보가 있는지 확인하는 방법이다.

이렇게 하면, 게이트를 출입하는 사람들마다 주민번호를 물어본 다음, 그 주민번호가 주민 리스트 안에 있다면 통과시켜주고, 주민번호가 없다면 통과를 제한하면 될 것이다.

이 방법을 사용하면, 주민들의 입장에서는 주민번호만 외운 뒤에 문지기에게 주민번호를 불러주기만 하면 성문을 통과할 수 있으니 편리하다.

다만, 문지기의 입장에서 생각해보자. 지금은 몇백 명의 주민들만 이 성 안에 살지만, 만약 이 성에 주민들이 몇 만, 몇 십만 명으로 늘어난다면, 엄청난 수의 주민들의 리스트를 관리하고, 성문을 통과할 때 빽빽하게 적힌 리스트를 하나하나 다 찾아봐야 할 것이라는 생각에 문지기는 기분이 아찔해진다.


2. 출입증 발급 (token)

당신이 두 번째로 생각해낸 방법은, 성 안에 거주하는 주민들에게 출입증을 발급하는 것이다.

성의 주민들에게만 출입증을 발급하고, 그 출입증에 영주의 도장을 찍어 위조 불가능하게 만든다면, 주민들은 문지기에게 출입증을 보여주고, 문지기가 도장의 진위 여부를 확인한 뒤 도장이 진짜라면 성 안으로 들여보내줄 수 있을 것이다.

이 방법을 사용하면, 주민들의 입장에서는 성을 출입할 때마다 출입증을 보여줘야 한다.

문지기의 입장에서는, 주민들의 리스트를 관리할 필요도, 찾아볼 필요도 없어졌지만, 주민들이 성문 안으로 들어올 때마다 도장이 진짜인지 아닌지 판단하기 위해 심혈을 기울여야 한다.


HTTP는 stateless하기 때문에, 서버는 클라이언트가 누구인지 매번 확인을 해야만 한다.

이를 위해 cookie, session, token 등을 활용한다.

쿠키는 클라이언트 로컬에 저장되는, 키와 값이 들어있는 데이터이다.

위 성문을 통과하는 주민 예시와 비교해 보면, 주민들은 성문을 통과하기 위해 주민번호를 외우거나 출입증을 가지고 있어야 하므로, 이 정보를 저장하기 위해 쿠키를 사용할 수 있는 것이다.

웹서버가 브라우저의 내부에 쿠키를 저장하고 싶다면, 웹서버에서 HTTP Response Header에 Set-Cookie 속성을 넣어 클라이언트에게 응답하면 된다. (문지기가 주민에게 주민번호 혹은 출입증을 발급해준 뒤, 주민에게 이 정보를 저장하라고 하는 것이다)

Set-Cookie 속성 안의 쿠키 정보를 읽은 클라이언트는 브라우저 내부에 쿠키를 저장하며, 이렇게 저장된 쿠키는 사용자가 따로 요청하지 않아도 브라우저가 서버에게 Request를 날릴 때마다 자동으로 넣어서 서버에 전송된다.

서버는 브라우저가 보낸 쿠키를 읽고 이전 상태 정보를 다시 떠올릴 수 있으며, 필요 시 쿠키를 업데이트하여 상태 정보를 변경할 수도 있다.

단, 해커가 HTTP 인증 정보를 중간에서 뺏어갈 수 있으므로, 주의가 필요하다.

쿠키에는 다음과 같은 요소들이 포함된다.

  • 쿠키의 이름 (key)
  • 쿠키의 값 (value)
  • 유효시간
  • 쿠키를 전송할 도메인
  • 쿠키를 전송할 경로


Session

session은 앞전의 예시에서 주민 리스트와 대응되는 개념이며, 서버 측에 정보를 저장하는 것이다.

서버에서는 클라이언트를 구분하기 위해 세션 ID(=주민번호)를 부여하며, 클라이언트가 접속을 종료할 때까지 인증상태를 유지한다(=주민번호를 주민 리스트에 유지한다).

세션 ID는 128 비트 이상으로 이뤄지기 때문에 brute forcing만으로 세션 ID를 가로채기는 매우 어려우며, 이 때문에 사용자를 식별하는 정보로 활용할 수 있다.

사용자에 대한 정보를 서버에 두기 때문에 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 된다.

위 그림에서는 쿠키를 매개로 하여 session id를 넘겼지만, session id는 cookie뿐만이 아닌 form field, URL의 형태로도 넘겨줄 수도 있다.


Token Based

session 방식에서는 server가 인증 정보(=주민 리스트)를 저장하는 반면, token based 방식에서는 서버 내부에 별도의 저장소가 필요 없다. 토큰은 인증에 필요한 모든 정보를 자체적으로 지니고 있다(self-contained).


JWT(Json Web Token)은 인터넷 표준 인증 방식 중 하나로, 다음과 같이 header, payload, signature가 .로 구분되어 concatenate된 구조(header.payload.signature)를 가지고 있다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZXNzYWdlIjoiSldUIFJ1bGVzISIsImlhdCI6MTQ1OTQ0ODExOSwiZXhwIjoxNDU5NDU0NTE5fQ.-yIVBD5b73C75osbmwwshQNRC7frWUYrqaTjTpza2y4

위 사진을 보면, JWT의 header, payload, signature 필드가 나타나 있다. 누구든지 왼쪽 내용을 오른쪽 내용으로 decoding하거나, 오른쪽 내용을 왼쪽 내용으로 encoding할 수 있다!

  • Header
    • 암호화에 사용할 알고리즘(alg, algorithm), 타입(typ, type) 등을 포함한다.
  • Payload
    • 서버에서 보낼 데이터를 포함한다. 포함될 수 있는 정보들은 다음과 같다.
      • 토큰 발급자(iss, issuer)
      • 토큰 제목(sub, subject)
      • 토큰 대상자(aud, audience)
      • 토큰 만료 시간(exp, expiration time)
      • 토큰 활성 날짜(nbf, not before)
      • 토큰 발급 시간(iat, issued at)
      • JWT 토큰 식별자(jti, JWT ID)
  • Signature
    • Header + Payload + Secret key를 합한 데이터가 header에서 장한 알고리즘으로 암호화된다.

위 header, payload, signature 중에서 가장 중요하게 보아야 할 것은 signature이다. 위 예시에서의 출입증 도장과 비교되는 개념이기 때문이다.


먼저, JWT 토큰을 서버가 생성해 클라이언트에게 주는 과정을 생각해보자(성에서 출입증을 주민에게 발급하는 과정).

  • 서버는 JWT 토큰에 토큰 발급자, 제목, 대상자, 만료 시간 등을 적는다. (출입증에 각종 정보를 적는다)
  • 서버 내부에는, 외부로 노출되지 않아야 하는 secret key가 있다(=도장).
  • 서버는 JWT 토큰의 헤더, 페이로드, secret key를 합친 값을 header에서 정한 암호화 알고리즘으로 암호화한다.
  • 서버는 암호화한 값을 signature 필드에 저장한다(=도장을 찍는다).
  • 서버가 클라이언트에게 JWT 토큰을 반환한다(=출입증 발급).

그 다음으로, 클라이언트가 JWT 토큰을 가지고 인증을 하는 과정을 생각해보자(주민이 성문을 통과하는 과정).

  • 클라이언트는 서버에게 JWT 토큰을 준다(=출입증 제출).
  • 서버는 JWT 발급 시와 동일한 과정을 거쳐, 그 결과값이 클라이언트가 준 JWT 토큰 내부의 signature 값과 동일한지 판단한다.
    즉, JWT 토큰의 header, payload, 비밀키 값을 합한 값을 header에서 정한 방식으로 암호화한 값과 비교한다.
    secret key는 서버만이 알기 때문에, 서버를 제외한 누구도 이렇게 암호화한 결과값과 동일한 값을 얻을 수 없다(=도장은 성 내부에만 있기 때문에, 성 밖의 누구도 완전히 똑같은 도장 모양을 찍을 수 없음).
  • 만약 암호화한 결과값이 클라이언트가 준 JWT 토큰 내부의 signature 값과 다르다면, 그것은 JWT 토큰의 header나 payload 값이 위조되었음을 나타낸다.
    header나 payload 값이 1 비트만 달라지더라도, 결과값은 완전히 달라지기 때문이다.
  • 만약 그렇지 않고 그 결과값이 signature 값과 일치한다면, 서버는 header와 payload 내부의 데이터가 위조되지 않은, 신뢰할만한 데이터라는 것을 알 수 있게 된다.
    서버는 payload 값에 적힌 데이터를 보고, 클라이언트를 인증해줄 수 있다.

여기서 짚고 넘어가야 할 것은, signature와는 달리 header와 payload는 암호화가 되지 않기 때문에 민감한 정보들을 담으면 안된다는 것이다. 따라서 이곳에는 인증에 필요한 정보들이 아닌, 단순히 사용자를 식별하기 위한 정보만을 담아야 한다.

JWT 토큰은 Web Storage (local storage/session storage)와 쿠키 등에 저장될 수 있는데, 이에 대한 정보는 아래 링크를 참고하자.

Where to store a JWT token properly and safely in a web based application?


references

비슷해보이지만 다른 두 친구를 소개합니다. Authentication vs Authorization

쿠키와 세션 개념

Cookie, Session, Token 의 차이점

쿠키, 세션, 토큰의 차이점

Using HTTP cookies

What are Session Cookies and do They Need a Cookie Consent?

Token Based Authentication Made Easy

JWT(Json Web Token) 알아가기

HTTP 헤더의 이해 Part 1

Where to store a JWT token properly and safely in a web based application?

profile
이예찬

0개의 댓글