Cookie/Session/Token(JWT) 인증 정리
최근에 자바 스프링 부트에 대해 공부하면서 로그인과 이를 유지할 방법에 대해 고민해보게 됐다. 보안과 관련되어 굉장히 중요한 부분이다보니 쿠키, 세션, 토큰의 특징과 차이점을 공부해볼 필요가 있다고 생각해 정리해보려고 한다.
HTTP의 특징
쿠키, 세션, 토큰의 특징과 차이점을 정리하기 전에 먼저 왜 해당 개념들이 나오게 됐는지 알 필요가 있다.
HTTP 프로토콜의 특성 중에 connectionless(통신 후 바로 연결을 끊어버리는 것), stateless(통신 후 어떤 상태도 기록해놓지 않는 것) 두 가지가 있는데, 위 특성들에 따라 클라이언트에서 로그인을 요청하게 되면 페이지를 이동할 때마다 로그인을 다시 해야한다.
왜냐하면 통신 후 연결은 바로 끊기게 되고 이전 상태에 대한 어떤 정보도 남아있지 않기 때문이다.
HTTP 프로토콜 방식이 처음에 나왔을 때는 이 방법이 효율적이었겠지만, 현재는 (인스타 같은 소셜 네트워크를 생각해보자) 로그인 후 게시물 쓰기, 팔로워들과 교류하는 기능 등을 구현하기 위해서는 로그인 정보를 반드시 유지해야 한다.
이를 보완하기 위해 쿠키, 세션, 토큰의 개념이 등장했다.
쿠키(Cookie) 기반 인증
쿠키란? 클라이언트(브라우저) 로컬에 저장되는 키-값의 데이터 파일
쿠키는 일종의 작은 기록 정보 파일을 의미한다. 뒤에 나올 단점들로 인해 보통 중요하지 않은 유저 정보에서부터 ‘장바구니’, ‘24시간 동안 보지 않기’, ‘자동 로그인 설정’ 등의 유저 옵션 정보를 담는데 사용된다.
브라우저는 쿠키를 저장한 후에 서버에 request 시 쿠키를 전송함으로써 사용자 인증을 유지할 수 있다.
이 때 인증 유효 시간을 설정할 수 있고, 유효 시간이 정해진다면 클라이언트가 종료되어도 쿠키가 유지된다.
인증 절차
- 클라이언트가 서버에 request를 보낸다.
- 서버는 자격을 검증 후 Set-Cookie 헤더를 포함해 response한다.
- 브라우저는 쿠키를 저장한다. 클라이언트는 모든 request에 쿠키 정보를 담아 서버에 전송하고 쿠키에 따른 데이터를 제공한다.
- 서버에서 쿠키를 읽어 이전 상태 정보에 대한 변경이 필요할 때 쿠키를 업데이트하여 헤더에 포함해 response한다.
- 브라우저가 종료되어도 쿠키 만료 기간이 남아있다면 클라이언트에서 보관한다.
장점
- 사용자의 상태 정보, 선호를 추적하는 데 용이하다.
- 쿠키의 사이즈(최대 4KB)가 작기 때문에 클라이언트에서 정보를 저장하기에 부담이 없다.
- 쿠키는 요청에 자동적으로 포함되기 때문에, 개발자가 이걸 수동으로 구현할 필요가 적어 용이하다.
단점
- 쿠키는 한 도메인 당 20개의 값만 가질 수 있고, 최대 300개까지 저장 가능해 한계가 있다.
- 웹 브라우저 마다 쿠키에 대한 지원 형태가 다르기 때문에, 브라우저간에 공유가 불가능하다.
- 사용자 인증에 대한 모든 정보를 클라이언트에서 가지고 있기 때문에, 쿠키 자체를 탈취 당해 사용자 정보를 모두 빼앗길 수 있다. 그래서 쿠키 자체는 보안과는 상관 없는 장바구니, 자동 로그인 설정, 24시간 동안 창 띄우지 않기 등에 이용할 수 있다.
세션(Session) 기반 인증
쿠키를 통해 로그인 상태를 유지할 수 있지만, 개인정보를 HTTP 프로토콜을 통해 주고받기 때문에 보안에 취약하다는 단점이 있다. 이를 보완한 개념이 세션(Session)이다. 세션은 클라이언트의 인증 정보를 서버에 저장한다.
세션은 쿠키를 기반으로 하고 있지만, 클라이언트의 정보를 브라우저가 아닌 서버에서 저장한다는 것이 큰 차이점이다.
인증 절차
- 유저가 로그인을 하고 세션이 서버 DB, 메모리(보통 Redis가 사용됨) 등에 저장됨. 이 때 세션을 식별하기 위한 SessionID를 기준으로 정보를 저장한다.
- 브라우저 쿠키에 Session ID가 저장된다.
- 브라우저는 서버에 request 시 Session ID를 담아 전송한다.
- 서버는 클라이언트가 보낸 SessionID와 서버 메모리에서 관리하고 있는 session ID를 비교하여 인증한다.
- 로그아웃 시 서버의 세션 정보를 삭제하고 클라이언트의 쿠키를 갱신한다.
아래는 구글에 접속했을 때 실제 세션이 쿠키 내에 저장된 사진이다. 키 값은 사이트에 따라 NID, SessionID 등 다양한 형태로 존재할 수 있다.

장점
- 쿠키보다 보안적인 측면에서 우수하다.
- 쿠키를 포함한 request가 외부에 노출되더라도 쿠키 방식과 달리 Session ID는 유의한 개인정보를 의미하지 않는다.
- 각 사용자마다 고유한 SessionID가 발급되기 때문에 요청이 들어올 때마다 회원정보를 확인하지 않아도 된다.
단점
- 서버에서 세션 정보를 저장하고 관리해야 한다는 부담이 있다.
- SessionID를 가로채면 해킹의 위험이 있다.
- 사용자가 많아질 수록 서버의 메모리에 부하가 커진다.
- 세션을 모든 서버에서 이용할 수 있어야 하기 때문에, 중앙 세션 저장소가 없을 경우 확장이 어렵다.
- 중앙 세션 저장소에 문제가 생기면 전체 인증에 문제가 발생할 수 있다.
이전에 웹 사이트 크롤링(스크래핑)을 해봤던 경험으로 볼 때, request header에 SessionID 값만 넣어주면 로그인이 필요한 페이지에 자유롭게 접속이 가능했다.
단순히 SessionID만 적어주면 됐었기 때문에 이를 가로채서 사용하게 되면 큰 문제가 있을 것 같다고 느꼈었다.
지금 생각해보니 Session으로만 인증을 하는 사이트가 굉장히 많은 것 같다..!
토큰(JWT) 기반 인증
토큰 기반 인증에서는 유저 정보를 클라이언트에 저장한다.
JSON WEB Token(JWT)는 클라이언트와 서버 간의 통신 시 JSON을 이용해 데이터를 전송하는 방법이다. 세션 방식처럼 토큰 자체를 쿠키에 담아 보내줄 수도 있고, HTTP Header에 담아 보내줄 수도 있다.
jwt.io를 이용해 JWT 토큰을 인코딩, 디코딩 한다.
JWT 토큰 구성
. 로 구분 짓는 3가지 문자열(Header, Payload, Signature)의 조합이다.
- Header: 토큰의 타입과 사용되는 알고리즘(HMAC, SHA256, RSA 등)에 대한 정보를 담고 있다.
- Payload: 토큰에 담을 클레임 정보를 포함하고 있다. Payload에 담는 정보의 한 조각을 클레임이라 부르고, 이는 key-value 한 쌍으로 이루어져 있다.
- Signature: 위변조 여부를 확인하기 역할을 담당한다. Header와 Payload는 단순히 인코딩 된 값이기 때문에, 복호화 및 조작이 쉽다. Signature는 서버에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다.
인증 절차
- 클라이언트 로그인 요청이 들어오면, 서버는 검증 후 클라이언트 ID 등의 정보를 Payload에 담는다.
- 서버는 비밀키를 이용해 JWT 토큰을 생성하고 클라이언트에 이를 response한다.
- 브라우저는 local storage, session storage 또는 cookie storage 등을 이용해 토큰을 저장한다.
- 이후 클라이언트 요청 시 JWT bearer에 의해 haader에 인증 정보를 추가하고 서버는 signature를 검증한다.
- 로그아웃 시에 서버와의 교류 없이 클라이언트의 토큰은 파기된다.
장점
- 서버에 별도의 JWT에 대한 데이터를 저장할 필요가 없다. 서버는 토큰을 발급, 갱신, 검증 과정만 실시하면 된다.
- 토큰 기반으로 다른 로그인 시스템에 접근 및 공유가 가능하기 때문에 분산 시스템에서의 확장성이 우수하다.
- 토큰 기반의 다른 인증 시스템을 이용할 수 있다. (소셜 로그인 등)
- JSON 기반이기 때문에 REST API에서도 잘 동작한다.
- 어떤 타입도 JWT에 정보를 저장할 수 있다.
단점
- JWT의 길이가 길어, 인증요청이 많아질수록 네트워크 부하가 심해진다.
- Payload는 암호화 되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
- JWT에 대한 정보를 서버에서 저장하지 않기 때문에 토큰을 탈취당하면 추척하기 어렵다.
- 발급된 JWT는 삭제가 불가능하다. 특정 사용자의 접속을 강제로 만료하기 어렵다.
보완 방법
찾아보니 JWT에 대한 단점을 보완하기 위해 여러 방안들이 사용되고 있지만, 완벽한 방법은 존재하지 않는 것 같다.
효율성과 보안성의 적절한 타협점을 찾는 것이 중요한 것 같고, 필요에 따라 다양한 형태로 구현하는 것 같았다.
아래는 위 단점을 보완할 수 있는 방법들이다.
- JWT의 짧은 만료 기한 설정
- Sliding Session: 글을 작성하는 도중에 session 만료된다면 작성한 글이 날라갈 수 있다. 이를 보완하기 위해 서비스를 지속적으로 이용하는 사용자에게는 토큰의 기한을 자동으로 늘려주는 방법을 사용한다. 즉, ‘글 작성’, ‘결제’ 등을 시작할 때 새로운 토큰 발급한다.
- Refresh Token: 클라이언트가 로그인 요청을 보내면 서버는 Access Token과 이보다 긴 만료 기간을 가진 Refresh Token을 발급한다. 클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청합니다. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 재발급한다. 해당 전략을 사용하면 Access Token의 만료 기한을 짧게 설정할 수 있으며, 사용자가 자주 로그인할 필요가 없습니다. 또한 서버가 강제로 Refresh Token을 만료시킬 수 있다. 그러나 검증을 위해 서버는 Refresh Token을 별도의 storage에 저장하고, 클라이언트도 탈취 방지를 위해 Refresh Token을 보안이 유지되는 공간에 저장해야 한다는 단점이 있다.
정리하면서 느낀 점 & 추후 공부할 부분
공부해보면서 느낀 점은 완벽한 보안 방법은 없다는 것이다.
보안을 강화하면 속도 등의 효율성 면에서 단점이 생기고, 효율성만 강조하자니 보안에 큰 구멍이 생기게 된다.
보안이 중요할지, 혹은 속도 등의 효율성이 중요할지 고민해볼 필요가 있을 것 같다.
여러 웹 사이트를 크롤링 했던 경험으로 볼 때, 이런 고민을 하지 않고 단순히 구현만 해놓은 사이트들도 많은 것 같다. 실제로 SessionID 만으로 로그인이 가능하고 유효기간이 무한인 사이트도 많이 봤다.
실제 대규모 트래픽이 있는 사이트에서는 어떤 방법들을 조합해 선택했을지 궁금하다.
댓글남기기