TL;DR
- 사용자가 사용한 RefreshToken을 가로채 새로운 AccessToken을 발급받을 수 있다.
- RTR을 적용하여 이러한 재전송 공격을 방지할 수 있다.
- 현재 발행되어 있는 토큰을 관리하기 위해 Redis, DB 등이 필요하다.
RefreshToken을 탈취당한다면?
이전 글에서 RefreshToken은 쿠키에 보관하여 JavaScript에서 직접 접근을 방지할 수 있다고 작성했습니다.
하지만 공격자가 토큰을 탈취하는 것을 완전하게 차단할 수는 없습니다.
우회하여 RefreshToken을 열람할 수 있죠.
아래는 공격 시나리오 중 하나입니다.
- 사용자가 AccessToken 갱신을 요청한다.
- 공격자가 전송되는 쿠키를 가로채 RefreshToken(RT1)을 탈취한다.
- 사용자는 신규 AccessToken을 저장하고, 갱신된 RefreshToken(RT2)는 쿠키에 담겨 전달된다.
- 공격자는 RT1을 이용하여 새로운 AccessToken을 발급받아 사용자와 동일한 권한을 갖는다.
토큰 인증 방식은 서버가 토큰을 관리하지 않고 유효한지만 판단합니다.
따라서 RefreshToken의 유효기간 이내라면 언제든 재사용이 가능합니다.
공격자가 중간자 공격 등을 통하여 RefreshToken을 탈취한다면, 새로운 AccessToken을 발급받을 수 있다는 의미입니다.
이렇게 사용자가 전송한 데이터를 다시 전송하는 공격 방식을 재전송 공격(Replay Attack)이라고 합니다.
게다가 토큰 인증 방식은 중복 로그인을 방지할 수 없다.
공격자가 새로 발급 받은 AccessToken과 사용자가 정상적으로 발급 받은 AccessToken은 동일한 권한을 갖게 되는 것이죠.
RefreshToken을 일회용으로
재전송 공격은 간단한 방법으로 보완이 가능합니다.
한 번 전송한 토큰은 다시 사용할 수 없도록 만들어버리는 방법이죠.
이러한 방식을 RefreshTokenRotation(RTR)이라고 합니다.
RTR 구현을 위해서는 기존 DB나 Redis 등을 활용해야 합니다.
아래는 RTR을 적용하여 보완한 시나리오입니다.
로그인
- 사용자가 ID와 암호로 AccessToken을 요청한다.
- DB의 계정 정보와 일치한 경우 신규 RefreshToken은 쿠키에 담아, AccessToken은 HTTP 응답으로 반환한다.
- RefreshToken을 별도 저장소에 저장한다.
- 클라이언트는 내부 변수로 은닉하여 AccessToken을 보관한다.
재접속
- 사용자가 페이지 전환, 새로고침 등으로 인하여 새로 접속한다.
- 페이지 초기화 시 RefreshToken 존재 여부를 확인한다.
- RefreshToken이 존재한다면 신규 AccessToken을 요청한다.
- 서버는 쿠키에 담겨져 전달된 RefreshToken을 검증한다.
- 검증에 성공했다면 RefreshToken을 무효화 하고, 신규 RefreshToken을 저장한다.
- 신규 RefreshToken은 쿠키에 담아, AccessToken은 HTTP 응답으로 반환한다.
- 클라이언트는 AccessToken를 JavaScript 내부 변수로 은닉하여 보관한다.
- 검증에 실패했다면 실패 응답을 클라이언트로 전달하고, 클라이언트에서는 RefreshToken을 파기한다.
RefreshToken 갱신 (RTR)
- AccessToken 요청 중 토큰 만기 임박 여부를 확인한다.
- 만기 임박한 경우 신규 AccessToken을 요청한다
- 서버는 쿠키에 담겨져 전달된 RefreshToken을 검증한다.
- 검증에 성공했다면 RefreshToken을 무효화 하고, 신규 RefreshToken을 저장한다.
- 신규 RefreshToken은 쿠키에 담아, AccessToken은 HTTP 응답으로 반환한다.
- 클라이언트는 AccessToken를 JavaScript 내부 변수로 은닉하여 보관한다.
- 검증에 실패했다면 실패 응답을 클라이언트로 전달하고, 클라이언트에서는 로그아웃 처리한다.
결론
세상엔 완벽한 보안은 없고, RTR도 완벽한 방법이 아닙니다.
아래와 같은 시나리오로 공격자가 접근할 수 있습니다.
- 사용자가 로그인 후 사용하지 않는다.
- 공격자가 쿠키를 탈취한다.
- 쿠키의 RefreshToken을 이용하여 사용자보다 신규 AccessToken을 발급한다.
- 이후 사용자는 로그인으로, 갱신된 RefreshToken으로 동일한 권한을 갖는다.
따라서 실제 서비스에선 클라이언트의 보안도 유의하여 운영해야 합니다.
더 안전하게 운영하기 위해선 아래와 같이 운영하는 것을 고려하는 것이 좋습니다.
- 동시 로그인 방지: 신규 로그인 시 기존 RefreshToken을 모두 무효화
- AccessToken 유효기간 단축: 30분 이내로 짧게 설정
- RefreshToken 유효기간 단축: AccessToken과 같은 시간으로 설정
참고 문서
- 인증과 인가를 안전하게 처리하기 (Refresh Token Rotation) - 개발새발.log (https://velog.io/@chchaeun/인증과)