1. 세션
HTTP는 기본적으로 상태가 없는(stateless) 프로토콜이기 때문에, 서버는 각 요청마다 사용자를 식별할 수 있는 방법이 필요하다.
이를 위해 사용자가 로그인하면 "로그인 정보"를 서버(DB)에 저장했다.
사용자 식별 문제를 해결됐지만, 서비스 규모가 커지면서 다른 문제가 생겨난다.
단일 서버 하나로는 방대한 사용자 요청을 처리할 수 없다는 것.
단일 서버의 크기를 늘리기에는 한계가 있다.
이때 고려되는 가장 효율적인 방법은 비슷한 크기의 서버를 여러대 두는 것. 한 서버에 요청이 과도하게 몰리면 다른 서버에 트래픽을 이동시키면 되니까!
이때, 사용자를 식별하는 "로그인 정보"는 각 서버마다 존재하므로, 세션 별 로그인 정보에 불일치 문제가 발생한다.
즉, 로그인 요청을 A서버가 처리하면 B서버에서는 로그인 여부를 알 수 없다. 세션이 많아질 수 록 엄격한 동기화가 필요해진다. 엄격한 동기화는 오히려 성능을 저하시키게 되는 것.
이를 해결하려는 다양한 방식이 있다.
2. 토큰
세션 방식을 채택할 때, 나타나는 확장성과 상태 관리 문제를 해결하고자 토큰 방식이 등장했고 JWT는 Json Web Token의 약자로 토큰 방식 중 하나이다.
세션은 로그인 정보를 서버에 두었다면 (인증 책임이 서버) 토큰은 로그인 정보를 클라이언트, 요청에 둔다. (인증 책임이 클라이언트)
방법은 다음과 같다.
사용자 정보를 담고, 서버가 가지는 비밀 키로 만들어낸 토큰을 사용자에게 건네준다.
그리고 서버에 요청을 보낼 때, 마다 서버는 해당 토큰에 대해서만 검증하는 것. 결국 토큰은 클라이언트에서 잘 간수해야하므로 인증 책임이 클라이언트에 있고 서버는 검증만 한다.
토큰 방식은 사용자에게 토큰을 건네주기 때문에 서버가 확장되더라도 비밀키만 같다면 토큰을 검증하는 데 문제될 게 없다.
하지만 인증 책임이 클라이언트에 있다는 말은 반대로 서버의 통제권이 없다는 말이다.
즉, 해커가 토큰을 탈취해서 자기가 발급 받은 사람인냥 행세를 한다면 어떻게 될까?
서버는 해커가 건넨 토큰은 유효하므로, 해커인 지 아닌 지 구별할 수 없이 OK하고 넘어간다.
결국 토큰은 보안에 관한 문제로 부터 항상 싸울 수 밖에 없다.
3. RefreshToken
위에 말한 대로 서버가 발급해준 토큰을 해커가 탈취당하면 해당 토큰의 만료기간 동안 해커는 자유롭게 사용자 정보를 빼돌릴 수 있다.
이를 방지하기 위해서 기존의 토큰(AccessToken)의 만료기간을 짧게하고 AccessToken이 만료되면 재발급 받을 수 있는RefreshToken을 도입한다
이럴 경우 AccessToken을 탈취당하더라도 만료기간이 짧기때문에 위험성을 낮출 수 있다는 것.
하지만 RefreshToken 마저 빼앗겨 버린다면? 결국 토큰의 만료기간이 끝날때까지 손가락 쪽쪽빨면서 기다려야한다.
4. 저 포기하겠습니다. Stateless
잃어버린 서버의 통제권을 되찾기 위해서는 어떻게 해야할까?
간단하다 로그인 정보를 서버에 저장하는 것!
따라서 대부분의 AccessToken은 DB에 저장하지 않지만, RefreshToken은 DB에 저장한다!
5. DB에 저장하면 뭐가 좋은데?
잃어버린 서버의 통제권을 되찾았는데 어떻게 써먹을 수 있을 까?
stateless를 포기하면서 까지 DB에 저장했는데 어떤 이점이 있을까?
여러 이점이 있지만, 그중 RTR 방식을 취하면 해커로부터 위험을 낮출 수 있다.
다음 상황을 보면 금방 이해할 수 있다.
RTR은 AccessToken을 RefreshToken으로 재발급 받을 때, RefreshToken도 같이 재발급 하는 것 (즉 RT가 일회용!)
1. 해커가 AT와 RT를 탈취하여, AT로 유저의 권한을 마음껏 즐긴다.
2. AT가 만료되어 RT으로 AT를 재발급 해야한다. (유저도 마찬가치)
이때 두 가지 상황이 있다.
유저가 토큰을 먼저 재발급 하는 경우 (3-1), 해커가 토큰을 먼저 재발급 하는 경우 (3-2)
3-1. 유저가 먼저 토큰을 재발급 하는 경우
유저가 먼저 AT를 재발급한다.
재발급 한 뒤 DB에 새로운 RT가 저장된다.
이럴 경우 해커는 기존에 사용된 RT가 DB에 저장된 RT와 다르기 때문에 재발급에 실패한다.
3-2. 해커가 먼저 토큰을 재발급 하는 경우
해커가 먼저 AT를 재발급한다.
재발급 한 뒤 DB에 해커가 새로 발급한 RT가 저장된다.
이럴 경우 일반 유저는 다시 로그인한다. 재 로그인 시 DB의 RT가 갱신 되어 해커가 가지고 있는 RT를 사용할 수 없습니다. (DB에 저장된 RT와 해커가 가진 RT 는 다르기 때문에)
RT를 DB에 저장하지 않는다면, RT가 탈취 됐을경우, 만료기간이 끝나기까지 기다리는 수 밖에 없습니다. (서버 통제X)
RT를 DB에 저장한다면 RT가 탈취되더라도, 사용자가 재 로그인하면 해커의 RT를 무력화시킬 수 있습니다.
6. 저장 위치
이렇게 나는 프로젝트를 진행하면서 AT와 RT를 도입했다.
근데 어디에 저장해서 보내지...?
ResponseBody, Response Headers, Cookie 셋 중 골라봐~
애초에 ResponseBody는 요청에 대한 응답 정보 (백엔드 입장에서는 웹페이지를 꾸미는데 필요한 데이터 정보)를 응답하는 목적이므로 토큰을 담는 위치로는 책임상 적합하지 않다. (+ 보안상에도 좋지 않다.)
따라서 ResponseHeaders에 보내거나 Cookie에 보내면 된당
보통은 AccessToken을 ResponseHeader에 더 높은 수준의 보안성이 요구되는 RefreshToken은 Cookie에 담아 보낸다.
일단 AcessToken을 헤더에 담아 보내면 프론트에서는 가장 안전하게 JS의 private 밸류로 설정하면 외부에서 접근이 불가능하고한다. (= 메모리 저장) 근데 이러면 사용자가 새로고침 하면 토큰이 날라가 버린다.
이래서 우리가 RefreshToken는 뉴진스 쿠키에 저장하는 거다.!! 토큰이 없으면 재발급 받으면 되니까!
Cookie는 HttpOnly으로 JS에서 접근을 못하게 막을 수 있고, Secure 옵션으로 Https 환경에서만 주고 받을 수 있다.
마지막으로 SameSite 설정으로 같은 Site가 아니면 접근을 못하게 막하 CSRF 공격으로부터 위험을 줄일 수 있다.
결론
난 앞으로 AccessToken과 RefreshToken을 헤더와 쿠키에 담아서 보내야지! 아~ 상쾌해
'프로젝트에서 일어난 일' 카테고리의 다른 글
졸프: 라즈베리파이와 MSA를 곁들인 (0) | 2025.04.10 |
---|---|
Jwt토큰 인가 검증에서 일어난 간단한 사건! (0) | 2025.02.04 |
N+1 싹둑(Slice)해버리기 (0) | 2025.02.01 |
카카오, 구글 통합 로그인 中 (의사소통의 중요성) (0) | 2025.01.16 |
허용한 경로까지 막아버리는 불효자 코드 물효자로 만들기 (0) | 2025.01.14 |