- JWT 토큰을 사용하는 경우는 크게 아래와 같을 것이다.
- 로그인 성공 시 JWT 발급 → 클라이언트로 JWT 발급
- 권한이 필요한 모든 요청에 대해 클라이언트 → 서버측으로 JWT 전송
이러한 흐름에 따라서 JWT 를 주고받는 요청들은 다음과 같은 고려사항이 있을 수 있다.
(권한이 필요한 경우, 회원 CRUD / 게시글 및 댓글 CRUD / 주문 서비스 등등의 서비스 단에서 발생하는 많은 요청 및 로직)
따라서 서버가 클라이언트로 발급한 이후에 JWT는 매시간 수많은 요청에 있어 클라이언트가 서버에 전달한다.
하지만, 이런 경우 해커는 클라이언트 측에서 XSS를 이용한다거나 HTTP 통신을 가로채서 토큰을 훔칠 수도 있기에 여러 기술을 도입하여 이를 방지하고 대비책이 필요하고 존재한다.
그 방법에는 다음과 같은 것들이 있다.
<1 > 다중 토큰 : Refresh 토큰과 생명주기
다중 토큰은 탈취 자체를 방지하면서도 만약 탈취 당한 경우를 고려하여 대비책을 세우는 것이다.
이를 위해 Access/Refresh 토큰 개념이 등장한다.
자주 사용되는 토큰은 Accesss 토큰으로 생명주기를 짧게(약 10분)하며 이 토큰이 만료되었을 경우 함께 받은 Refresh 토큰(24시간 이상)으로 재발급 받는 형태이다.
이를 사용한 시나리오는 다음과 같다.
(1) 로그인 성공 → 생명주기와 활용도가 다른 토큰 2개 발급 : Access/Refresh
- Access 토큰 : 권한이 필요한 모든 요청 헤더에 사용될 JWT로 탈취 위험을 낮추기 위해서 약 10분 정도의 짧은 생명주기를 가진다.
- Refresh 토큰 : Access 토큰이 만료되었을 때 재발급 받기 위한 용도로만 사용되며 약 24시간 이상의 긴 생명주기를 지닌다.
(2) 서비스를 사용함에 필요한 모든 요청 : Access 토큰을 통해 요청
- Access 토큰만 사용하여 요청하기 때문에 Refresh 토큰은 호출 및 전송율 빈도가 낮다.
(3) 권한이 알맞다는 가정하에 2가지 상황
- 서버에서 알맞은 응답을 해준다 / 토큰이 만료된 경우 응답을 해준다.
(4) 토큰이 만료된 경우 Refresh 토큰으로 Access 토큰 발급
- Access 토큰이 만료되었다는 요청이 들어왔을 경우 프론트엔드 로직에 의해 1)번에서 받은 refresh 토큰을 가지고 서버의 특정 경로(Refresh 토큰을 받는 경로)에 요청을 보내어 Access 토큰을 재발급 받는다.
(5) 서버에서는 Refresh 토큰을 검증 후에 Access 토큰을 새로 발급한다.
< 2 > 다중 토큰, 즉 Access/Refresh 토큰의 구현 포인트
- 로그인이 완료되면 Access / Refresh 토큰 2개를 발급해서 응답한다. 각 토큰은 각기 다른 생명 주기를 지니며, Refresh 토큰의 경우에는 Access 토큰이 지닌 정보와 같이 회원 정보를 제외한 다른 정보를 지닐 필요가 없다. 따라서 회원 정보에 해당하는 payload 정보만 가진다.
- Access 토큰 요청을 검증하는 JWTFilter에서 Access 토큰이 만료된 경우는 프론트 개발자와 협의 된 상태 코드와 메세지를 응답한다.
- 프론트측 API 클라이언트(axios/fetch) 요청 시 Access 토큰이 만료 요청이 온다면 예외문을 통해서 Refresh 토큰을 서버측으로 전송하고 Access 토큰을 발급 받는 로직을 수행한다. ( 기존의 Access는 제거 )
- 서버측에서는 Refresh 토큰을 받을 엔드포인트(컨트롤러)를 구성하여 Refresh를 검증하고 Access를 응답한다.
< 3 > Refresh 토큰이 탈취되는 경우
단일 → 다중 토큰으로 전환하며 자주 사용되는 Access 토큰이 탈취되더라도 생명주기가 짧아 피해 확률이 줄었다.
하지만 Refresh 토큰 또한 사용되는 빈도가 적을 뿐 분명히 탈취될 수 있는 확률은 존재한다. 따라서 Refresh 토큰에 대한 보호 방법도 필요하다.
- Access/Refresh 토큰의 저장 위치 고려
→ 로컬/세션 스토리지 및 쿠키에 따라 XSS, CSRF 공격의 여부가 결정되기 때문에 각 토큰 사용처에 알맞는 저장소 설정이 필요하다.
- Refresh 토큰 Rotate
→ Access 토큰을 갱신하기 위한 Refresh 토큰 요청 시 서버측에서 Refresh 토큰도 재발급을 진행하여 한번 사용한 Refresh 토큰은 재사용하지 못하도록 한다.
(3-1) > Access/Refresh 토큰의 저장 위치 고려
→ 클라이언트에서는 서버한테 발급 받은 JWT를 저장하기 위해 로컬 스토리지와 쿠키에 대해 많은 고려를 한다. 각 저장소에 따라 고려되는 취약점은 다음과 같다.
- 로컬 스토리지 : XSS(==자바스크립트 공격) 공격에 취약함 → ‘통용적으로’ Access 토큰을 저장
- httpOnly 쿠키 : CSRF 공격에 취약함 → ‘통용적으로’ Refresh 토큰을 저장
(위와 같은 설정은 필수적이지 않다. 주관적인 판단에 따라서 커스텀하면 된다. 이때 고려가 필요한 사항들은 다음과 같다.)
- 고려
→ JWT의 탈취는 보통 XSS 공격으로 로컬 스토리지에 저장된 JWT를 가져간다. 한편, 쿠키에 저장하는 것은 CSRF 공격에 취약하다. 따라서 각 상황에 맞춰 알맞게 저장소를 선택하는 것이 필요하겠다.
→ Access 토큰의 경우, 권한이 필요한 대다수의 경로에 사용될 것이기에 CSRF의 공격을 받는 것보단 XSS의 공격을 받는 게 더 합리적 선택일 수 있다. 따라서 주로 로컬 스토리지에 저장되는 경우가 많으며, 에디터 및 업로더에서 XSS를 방어하는 로직을 작성하여 최대한 보호할 방법을 강구할 수 있지만, CSRF의 경우 클릭 한번으로 단기간에 요청이 될 수 있기 때문이다.
→ Refresh 토큰의 경우, 주로 쿠키에 저장한다. 비록 쿠키는 XSS 공격을 받을 수 있지만, HttpOnly를 설정하면 완벽히 방어가 가능하다. 한편, 그럼 CSPF 공격에는 어떻게 방어하지? 라는 의구심이 들 수 있는데, Refresh 토큰 단 하나의 토큰에 대한 재발급 경로이다. 즉 CSRF는 실질적 서비스를 사용하는 Access 토큰의 접근에 민감하게 다뤄줘야 하는 것이 합리적이다. 따라서 토큰 재발급 경로인 Refresh 토큰에는 CSRF 공격이 크게 피해를 입힐 로직이 없다고 고려할 수 있기 때문이다.
- CSRF VS XSS ??
스프링부트 강좌 51강(블로그 프로젝트) - XSS와 CSRF
(3-2) Refresh Token Rotate
→ 이와 같이, 저장소의 특징에 알맞게 JWT 보호 방법을 수행하여도 두 토큰 모두 탈취 당할 가능이 있다. 따라서 생명 주기가 긴 Refresh Token에 대한 추가적인 방어 조치가 필요합니다.
이것이 Refresh Token Rotate 방식인데, Access 토큰이 만료되어 Refresh 토큰을 가지고 서버 특정 엔드포인트에 재발급을 진행하면 Refresh 토큰 또한 재발급하여 프론트측으로 응답하는 방식이 Refresh Token Rotate이다.
< 4 > 탈취와 관련한 Refresh 토큰 주도권
<문제 상황>
→ 만약 로그아웃을 진행하면 JWT 가지고 프론트측에 존재하는 Access/Refresh 토큰을 제거한다. 그럼 프론트측에서는 JWT 토큰이 없기에 로그아웃 되었다고 여기지만, 만약 JWT이 탈취된 상태에서 서버에 요청을 보낸다면, 문제없이 요청이 받아진다. 이는 JWT의 State less한 특징 때문이며, 서버측은 JWT 상태에 대한 아무런 주도권을 가지지 않아 발생하는 문제이다.
비단 로그아웃과 더불어 토큰이 탈취되었을 경우, 악용되는 사례에 있어 토큰의 생명주기가 끝나기를 기다리는 것 뿐이다.
<해결 방안>
(1)
이러한 문제에 대해서 생명주기가 긴 Refresh token 발급과 서버 측에서 이를 저장하여 관리하고 요청에 따른 존재 여부를 확인하는 것이다. (서버 측에도 주도권을 부여)
만약 로그 아웃을 진행하거나 탈취에 의한 악용이 진행되는 경우 서버 측에서 해다 JWT를 삭제하여 피해를 방어할 수 있기 때문이다.
(Refresh 토큰 블랙리스팅)
(2) 로그인시 메일 알림
웹 사이트를 사용하다보면 평소 사용하지 않던 ip나 브라우저에서 접근할 시, 사용자의 계정으로 메일 알림이 발생한다.
이 경우 본인이 아닐 경우 “아니요”를 클릭하면, 서버측 토큰 저장소에서 해당 유저에 대한 Refresh 토큰을 모두 제거하여 앞으로의 인증을 막을 수 있게 만든다.
'Spring > 시큐리티' 카테고리의 다른 글
Refresh 토큰 서버측 저장 (0) | 2024.06.23 |
---|---|
Refresh 토큰 Rotate (0) | 2024.06.23 |
Refresh 토큰으로 Access 토큰 재발급 (0) | 2024.06.23 |
Access 토큰 필터(JWTfIlter) (0) | 2024.06.23 |
다중 토큰 발급(refresh/access) (0) | 2024.06.23 |
댓글