본문 바로가기

Trouble Shooting/Security

JWT - 공격자 방어 with Access Token, Refresh Token

 

 

 

 

백엔드를 개발하다 보면 사용자 인증/인가로 JWT를 많이 사용할 것이다.

JWT를 사용하면 기본적으로 서버가 클라이언트의 상태를 보존하지 않는다 (stateless 하다).

즉, 매번 서버의 리소스를 접근하기 위해 access를 던질 때마다 해당 유저가 유저 본인인지 공격자인지 구별하지 못한다.

 

 

 

따라서, 이런 문제를 해결하는 방안으로 리플래시 토큰을 추가적으로 사용한다.

이 토큰의 용도는 사용자 인증이 아닌 새로운 엑세스 토큰을 추가적으로 생성하는 용도로만 사용된다.

 

엑세스 토큰 : 서버의 자원에 접근하기 위한 토큰

리플래시 토큰 : 엑세스 토큰을 발급하기 위한 토큰

 

 

엑세스 토큰 탈취, 리플래시 토큰 없음

이를 활용해, 아래와 같은 원리로 공격자 방어를 수행한다.

1. 엑세스 토큰의 유효기간은 가능한 짧게 설정한다.

2. 리플래시 토큰은 유효기간을 가능한 길게 설정한다.

3. 사용자는 두 토큰을 모두 서버에 전송해 엑세스 만료시 리프래시를 통해 엑세스 토큰을 재발급 받는다.

- 공격자는 엑세스를 훔치더라도 짧은 시간만 사용가능하다.

- 사용자는 리플래시를 통해 엑세스 만료시 재발급을 받을 수 있다.

 

즉, '엑세스 토큰 탈취'에 대한 방어 방법에 대한 내용이다.

원리를 간략하게 설명하면 엑세스 토큰의 유효기간을 최대한 낮추어 엑세스 토큰이 탈취되더라도 피해를 최소한으로 한다는 의미이다.

또한, 기존의 사용자는 리플래시 토큰을 사용함으로써 엑세스 토큰의 짧아진 유효기간으로 발생하는 계속적인 로그인 불편함을 해소할 수 있다. 당연하게도 리플래시 토큰이 없다면 공격자는 새로운 엑세스 토큰을 발급받지 못한다.

 

 

 

리플래시 토큰 탈취, 엑세스 토큰 없음

그렇다면 리플래시 토큰의 탈취는 어떻게 대비를 해야하나?

방식은 우선 아래와 같다.

- 데이터베이스에 사용자마다 갖는 두개의 토큰을 저장한다.

- 사용자는 위의 방식을 그대로 사용해 자원에 엑세스한다.

- 공격자가 만약 리플래시 토큰을 탈취했고 엑세스 토큰은 없다는 가정하에 해당 리플래시 토큰을 통해 공격자는 엑세스 토큰을 발급 받을 수 있다.

- 만약, 엑세스 토큰의 유효기간이 만료되지 않았음에도 불구하고 새로운 엑세스 토큰을 요청했다면 데이터베이스에서 모든 정보를 삭제한다. 이 후, 사용자에게 알리고 새로운 로그인을 통해 두 토큰을 재발급 받게 한다. 이렇게 된다면 공격자는 해당 유저의 아이디/비밀번호 정보가 없기에 더이상 해당 유저의 정보를 통해 자원을 엑세스하지 못한다.

- 새로운 로그인에 대해서 기존 사용자에게도 공격자처럼 적용되어 모두 삭제되어 다시 로그인을 해야한다.

 

 

두 토큰 둘 다 소유

- 이 부분에 대해서는 리플래시 토큰이 완료되기까지 기다릴 수 밖에 없다고 한다.

 

 

정리

JWT를 통해 인증/인가 구현시 서버는 세션처럼 클라이언트의 로그인 여부를 만료시킬 수 없다. 따라서, 공격자가 토큰 취득시 이를 판단하고 만료시키기 어렵다. 따라서, 토큰 사용시 Redis와 같은 NoSql 데이터 베이스를 통해 이를 관리할 필요가 있는 것이다. 하지만 JWT의 장점인 저장공간이 기본적으로 들지 않는다는 점을 포기해야한다.

 

결론적으로 사용자는 토큰을 쿠키, 로컬스토리지 중에 보관을 하고 서버는 데이터베이스에 보관한다. 특히, http-only 속성이 부여된 쿠키에 저장하는 것이 권장되는데 이는 속성 설정시 자바 스크립트 환경에서 접근이 불가능하기에 프론트 보안 문제인 xss, csrf가 발생하여도 토큰이 누출되지 않기 때문이다.

 

 

 

 

 

 

 

참고 : https://velog.io/@park2348190/JWT에서-Refresh-Token은-왜-필요한가