[Spring Security] (6) 액세스 토큰과 리프레시 토큰

Park Yeongseo·2023년 10월 9일
0

Spring Security

목록 보기
6/13
post-thumbnail

1. 액세스 토큰

액세스 토큰은 권한이 필요한 리소스에 접근하기 위해 필요한 토큰이다. 시리즈 (5)에서 말했던 것처럼 토큰은 서버 측에서 사용자를 식별할 수 있다면 무엇이든 될 수 있으므로 액세스 토큰을 구현하기 위해 꼭 JWT를 쓸 필요는 없지만, 주로 JWT를 쓴다.

1.1. 필요한 정보

액세스 토큰에는 권한이 있는 사용자인지를 확인하기 위해 액세스 토큰에는 사용자를 특정할 수 있는 정보가 들어가야 한다. 물론 JWT의 경우 쉽게 디코딩할 수 있으므로 사용자에게 지나치게 민감한 정보가 들어가서는 안 된다.

그런데 토큰의 경우 원칙 상 한 번 발급되면 서버 측에서 강제로 만료시킬 수가 없으므로, 액세스 토큰이 탈취되는 경우 해당 액세스 토큰 사용자의 권한에 허용된 리소스에 무제한적으로 접근할 수 있게 된다는 문제가 발생한다.

따라서 액세스 토큰에는 일정 기간의 짧은 유효 기간을 두어 계속해서 리소스에 접근하는 경우를 방지한다. 만약 이 유효 기간이 지나 토큰이 만료되면 해당 토큰으로는 리소스에 접근 불가능하게 되며, 다시 로그인을 해서 토큰을 재발급 받아야 하게 된다.

2. 리프레시 토큰

탈취의 가능성으로 액세스 토큰에 짧은 유효 기간을 두는 것은 좋지만, 액세스 토큰이 만료될 때마다 계속 로그인을 해야만 한다면 사용자에게는 큰 불편이 될 수 있다.

리프레시 토큰은 액세스 토큰의 갱신을 위한 토큰으로, 액세스 토큰보다 상대적으로 긴 유효 기간을 가지고, 재 로그인 없이 만료된 액세스 토큰을 새로 발급할 수 있게 한다.

리프레시 토큰 또한 액세스 토큰과 마찬가지로 유효 기간을 설정해 로그인 할 때 액세스 토큰과 함께 발급해 준다.

2.1. 리프레시 토큰의 관리

리프레시 토큰에도 사용자 정보를 담아 stateless하게 관리할 수도 있다. 하지만 stateless하다는 것은 서버에서 해당 리프레시 토큰이 정말로 우리가 발급한 그 토큰인지를 확인하지 않고 해당 토큰에 담긴 정보를 믿고 사용하겠다는 것이다. 리프레시 토큰에 대한 서버의 검증이 없다면, 해당 리프레시 토큰의 유효 기간 동안은 무제한적인 액세스 토큰이 발급해지고, 액세스 토큰에 비해 유효 기간이 긴 리프레시 토큰의 특성 상 이는 바람직하지 않다. 따라서 리프레시 토큰의 경우 서버의 검증 절차가 필요하며, 이러한 검증을 위해 서버에서는 보통 토큰을 발급할 때 해당 리프레시 토큰을 DB에 관리한다.

3. 플로우

액세스 토큰과 리프레시 토큰을 이용한 인증 과정의 플로우에도 사실 딱 정해진 것은 없고, 서비스에 따라 달라진다. 이 시리즈에서는 아래와 같은 플로우로 진행될 예정이다.

(하늘색은 프론트엔드, 연두색은 서버)

로그인이 성공하면 서버에서는 액세스 토큰 및 리프레시 토큰을 발급해준다. 프론트엔드에서 발급 받은 액세스 토큰을 이용해 서버에 리소스를 요청하면 서버에서는 액세스 토큰의 유효성을 검증하고 유효한 경우 요청된 리소스를 제공한다.

만약 액세스 토큰이 만료되거나 해서 유효하지 않은 경우, 서버는 프론트에서 리프레시 요청을 하게 한다. 리프레시 요청이 들어온 경우에는 리프레시 토큰의 유효성을 확인하고 유효하다면 새로운 액세스 토큰과 리프레시 토큰을 발급하고, 유효하지 않다면 로그아웃 처리해 새로 로그인하게 한다.

4. 액세스 토큰과 리프레시 토큰의 저장 및 관리

액세스 토큰은 서버에서 따로 저장하지 않고 stateless하게 이용하고, 리프레시 토큰은 서버에서 발급한 바로 그 토큰인지를 확인하기 위해 DB에 저장하겠다고 했다.

그럼 브라우저에서는 이 토큰들을 어디에 저장하고 관리해야 할까?

4.1. 웹 스토리지

웹 스토리지에는 세션 스토리지, 로컬 스토리지가 있다. 웹 스토리지는 사용하기에는 편하지만 자바스크립트 코드를 이용해 언제든지 접근할 수 있기 때문에 다음의 XSS 공격의 취약점을 가진다.

(1) 취약점 : XSS(Cross site scripting, 교차 사이트 스크립팅)

XSS는 서버에 노출된 웹 서비스에 스크립트를 주입해 다른 사용자가 실행하도록 하는 공격이다.

예를 들어 누군가가 게시판에 자바스크립트 코드를 게시했는데 이 코드가 따로 검증되지 않고 브라우저에서 실행된다고 해보자. 웹 스토리지는 자바 스크립트 코드를 이용해 접근할 수 있으므로, 만약 액세스 토큰이 웹 스토리지에 담겨 있다면 위와 같이 실행되는 코드를 통해서 사용자가 알지 못하는 사이 해당 액세스 토큰을 탈취할 수 있게 된다.

따라서 자바스크립트로 접근할 수 있는 웹 스토리지에 액세스 토큰을 담는 것은 좋은 선택이라 할 수 없다.

4.2. 쿠키

그렇다면 토큰을 쿠키에 담아 저장하는 건 어떨까? 쿠키 또한 기본적으로는 자바스크립트를 통해 읽을 수 있는 값이지만, HTTP Only 속성을 설정하면 브라우저에서 쿠키에 접근할 수 없게 할 수 있으므로 브라우저에서의 자바스크립트를 이용한 토큰 탈취는 예방할 수 있다.

하지만 이 경우에는 브라우저에서의 자바스크립트를 이용한 쿠키 탈취가 아니라 네트워크 감청을 이용한 토큰 탈취는 예방할 수 없다. 이를 예방하기 위해서는 쿠키에 Secure 설정을 해줘야 한다.

Secure Cookie는 HTTPS 프로토콜로 데이터를 암호화하고, HTTPS를 사용하지 않는 경우에는 쿠키를 전송하지 않게 하기 때문에 네트워크 감청을 통한 토큰 탈취를 예방할 수 있다.

하지만 쿠키는 여전히 다음의 CSRF 공격에 대한 취약점을 가지고 있다.

(1) 취약점 : CSRF(Cross site resource forgery)

이번에는 공격자가 어떤 API가 담긴 링크를 게시판에 올려 해당 링크를 클릭하면 해당 API의 요청을 서버로 보낼 수 있게 했다고 해보자. 만약 정상적으로 로그인한 사용자가 해당 링크를 누르면, 이 API 요청은 공격자가 아닌 정상 사용자가 보낸 요청이 된다. 공격자는 토큰을 따로 탈취할 필요 없이 정상 사용자에게 원하는 API를 실행할 수 있게 되는 것이다.

쿠키는 항상 요청에 함께 담겨 보내진다. 이 요청이 단순히 리프레시 요청인 경우에는 액세스 토큰의 재발급만 일어나 그다지 큰 위협이 되지는 않을 것이므로 리프레시 토큰의 경우에는 HTTP Only, Secure 설정을 한 쿠키에 담아도 비교적 안전하다.

하지만 만약 액세스 토큰도 쿠키에 담겨 있어 권한이 필요한 API 요청(예컨대 유저 정보 삭제나 글 삭제)을 성공적으로 보낼 수 있게 된다면 문제가 당연히 발생하게 된다.

4.3. 자바스크립트 변수

따라서 액세스 토큰도 쿠키에 담아 관리하는 것은 그다지 안전한 선택이라 할 수 없다. 그렇다면 액세스 토큰은 어디에 담아서 관리해야 할까? 액세스 토큰의 경우에는 자바스크립트의 로컬 변수로 담아 관리하는 게 가장 안전하다.

(1) XSS 공격으로부터 비교적 안전

  • 자바스크립트 내부 변수에 액세스 토큰을 담는다면 공격자가 자바스크립트 코드를 통해서 이를 확인할 수 없다.

(2) CSRF 공격으로부터 비교적 안전

액세스 토큰은 요청의 Authorization 헤더에 담겨 보내져야 한다. 공격자가 API 요청 링크를 심더라도 해당 요청의 헤더에 어떤 값을 넣을지는 알지 못하므로 CSRF 공격으로부터도 비교적 안전하다.

(3) 고려해야 할 점

다만 자바스크립트 변수에 액세스 토큰을 저장하면, 새로고침을 하거나 외부 링크로 나갔다가 돌아오는 경우 휘발이 돼버린다. 보안 상으로는 보다 안전하겠지만, 사용자 경험에서는 좋지 않을 수 있다. 이렇게 액세스 토큰이 휘발되는 경우, 쿠키에 담긴 리프레시 토큰으로 적절히 갱신 처리하는 로직을 구현할 필요가 있다.

4.4. 마무리

이 시리즈에서는 액세스 토큰은 자바스크립트 변수(React State)에, 리프레시 토큰은 HTTP Only, Secure 쿠키에 담아 관리하도록 할 예정이다. 다음 글부터는 Spring Security 내에서의 유저 인증 및 토큰 발급 구현을 시작하도록 하겠다.

0개의 댓글