세션과 쿠키 그리고 JWT에 대해 공부하고 겜린더에는 왜 JWT를 적용했는지 정리하기 위한 글이다.
세션을 알아보기 전에 HTTP에 대한 정리를 해보면 좋을 것 같다.
HTTP는 HyperText Transfer Protocol의 약자로 www(World Wide Web) 상에서 정보를 주고 받을 수 있는 프로토콜이라고 한다.
주로 HTML 문서를 주고받는 데 쓰이지만 일반적인 텍스트, 이미지, 영상, 음성, 파일 등도 전송할 수 있다.
클라이언트와 서버 사이에 이루어지는 요청/응답(Request/Response) 프로토콜인데
예를 들어 클라이언트가 HTML을 요청하면 해당 필요한 정보를 해당 클라이언트에게 전달한다.
이건 사실 1.0의 대표적인 특징이고 1.1부턴 지속 연결을 사용한다... 특징이라고 하긴 좀 애매하지만 그래도 가져와 봤다.
HTTP에서는 서버가 클라이언트의 상태를 보존하지 않는다.
응답과 요청이 독립적인데 이를 통한 장단점이 존재한다.
장점 : 서버 확장성이 높다
응답 서버를 쉽게 바꿀 수 있어 서버 증설하기 좋은 편이다.
단점 : 클라이언트가 추가 데이터를 전송해야 한다.
그럼, 로그인 같은 유저의 상태(정보)를 유지하는 서비스는 어떻게 하는 거지?
이럴 때
브라우저 쿠키, 세션, 토큰 등을 이용해 상태를 유지해 주는 것이다.
HTTP Cookie란 웹 서버에 의해 사용자의 컴퓨터에 저장되는 "이름을 가진 작은 크기의 데이터"(4KB) 이다.
사용자가 어떠한 웹사이트를 방문할 때 사용자의 웹 브라우저를 통해 인터넷 사용자의 컴퓨터나 다른 기기에 설치되는 작은 기록 정보 파일을 일컫는다.
쿠키는 스파이웨어를 통해 유저의 브라우징 행동을 추적하는 데 사용될 수 있고 누군가의 쿠키를 훔쳐 해당 사용자의 웹 계정 접근권한을 획득할 수도 있다.
쿠키는 보통 세 가지 목적을 위해 사용된다.
1. 세션 관리
요즘은 Web Storage를 사용해 정보를 저장하는 것을 권장한다고 한다.
요청마다 쿠키가 함께 저장되기 때문에 특히 Mobile Data Connections인 경우 성능이 급격히 떨어질 수 있다.
정보를 클라이언트 측에 요청하려면 Web Storage의 종류인 Local storage, Session storage 아니면 Indexed DB를 사용하면 된다.
Velog도 야무지게 사용한다.
근데 Local storage에 저장하면 XSS공격에 취약하지 않을까...?
차라리 HttpOnly, Secure, SameSite 플래그가 설정된 쿠키에 저장하는 게 나을지도..
Please Stop Using Local Storage 좋은 글이 있어 공유해본다.
HTTP는 상태가 유지되지 않는 Stateless 한 프로토콜이라고 했다. 그럼, 서버는 클라이언트의 상태를 어떻게 기억할까?
클라이언트의 상태를 기억하기 위해 대표적으로 Session 개념을 사용하였다.
서버는 고유한 세션 ID(JSESSIONID)를 웹 브라우저 쿠키로 전달한다.
이를 받은 클라이언트는 서버에 어떠한 요청을 할 때 쿠키(JSESSIONID)를 함께 전달한다.
서버는 전달받은 세션 ID를 서버에 이미 저장되어 있는 정보와 비교해 클라이언트의 상태를 지속적으로 유지하게 된다.
Session ID가 저장된 쿠키를 이용한 HTTP Request 과정
서버에 세션 저장소가 필요하다 (메모리, 데이터베이스 등)
클라이언트에는 세션 ID만 저장하는데 보통 쿠키에 저장한다.
Session ID는 브라우저 단위로 저장되어 브라우저 종료 시 소멸한다.
사용자의 로그인 상태, 닉네임 등 사용자가 요청할 때마다 필요한 정보들을 세션에 담아두면 사용자 DB에 접근할 필요가 없어 효율적이다.
Stateful 하고 서버 측에서 세션 관리 및 제어가 용이하다.
JWT(JSON Web Token)은 JSON 객체로 당사자 간에 정보를 안전하게 전송하기 위한 컴팩트하고 독립적인 방식을 정의하는 개방형 표준이다. (RFC 7519)
라고 하면 너무 복잡해 간단하게 정리해 보자
서버가 사용자 인증 후 JWT를 생성하여 클라이언트에 전송
클라이언트는 이후 요청마다 JWT를 함께 전송
서버는 JWT의 서명을 확인하여 유효성 검증
서버는 JWT를 디코딩해 유효성 검증을 진행한다.
추후 겜린더 서비스가 만약 커진다면 개발자들에게 겜린더의 데이터를 Open API로 배포하고 싶은 목표가 있었다. (누가 쓸지는 모르겠다만...)
이럴 땐 서비스 확장에 유리한 JWT가 더 적합하다고 판단하였다.
라즈베리파이로 서버를 일단 구성해야하기 때문에 제한된 리소스 환경에선 세션보단 JWT가 서버 메모리 사용을 줄일 수 있다고 생각한다.
세션 저장 및 조회할 때 서버에 대한 부하가 증가할 것 같아 JWT가 더 좋다고 생각했다.
보안 이슈가 생기면 정말 골때리기 때문에 신중하게 구현해야 한다. 그래서 내가 할 수 있는 방법들을 생각해 보았다.
이를 위해 Access Token의 만료시간을 짧게 할 예정이다.
Refresh Token은 Token Rotation 기능을 구현해 Refresh할 때마다 새로운 Token을 발급받을 수 있도록 하였다.
(Redis를 적극 활용하였다)
애초에 토큰에 민감한 정보를 많이 넣지 않을 예정이다.
자칫하면 DB 조회가 좀 더 늘어날 수 있지만 그래도 Token 자체에 예민한 개인정보를 넣는 것보단 나을 것 같다.
HTTPS 구현
앱 보안
로그아웃 시 안전하게 토큰 삭제
토큰 저장할 때 Local Storage 쓰는 게 아닌 좀 더 안전한 저장소 사용 (iOS의 Keychain, Android의 EncryptedSharedPreferences 같은 것들...)
겜린더는 확장성이 중요하다고 생각해 JWT를 구현하였지만,
언제나 기술의 장단점을 알고 명확하게 왜 써야 하는지 그리고 단점을 최대한 보완하는 것이 중요하다고 생각했다.
사실 예전에 Spring Boot로 Session 로그인을 구현한 적이 있었다. 근데 사실 구현하기가 너무 힘들었다.
레퍼런스가 많이 없었기 때문이었다.(스프링인데..?)
다들 JWT로 구현하는것이 유행인 것 처럼 로그인 기능을 구현하는 강의나 그런걸 찾아볼 때 전부 JWT로 구현하는 것을 보았었다.
기술에도 트랜드가 있다고 하지만 왜 써야 하는지 명확하게 모르면 안된다고 생각해 이번 글을 정리해보았다.
다음 글은 JWT를 어떻게 구현하였는가에 대해 적어볼 예정이다.