RefreshToken(feat. RTR)

0_0_yoon·2022년 11월 15일
0
post-thumbnail

문제상황

F12 프로젝트에서 인가를 JWT 기반으로 구현했다.
백엔드에서 1 시간 유효기간을 가진 JWT 기반 토큰을 발급해 주면 프론트엔드는 이를 세션 스토리지에 저장해서 사용했다.
이때 문제점은 두 가지였다.
첫 번째 사용자가 1 시간마다 로그인을 다시 해야 하는 점,
두 번째 보안 문제, 세션 스토리지는 자바스크립트로 읽어 올 수 있다. 즉 해당 토큰이 제3의 악성 사용자에 의해 탈취(XSS)당할 위험이 있다.
세 번째 탈취 사실을 알게 되어도 서버에서 별다른 조치를 할 수 없다.

원인

첫 번째 추가적인 조치 없이 JWT 기반 토큰 자체만으로 인가를 구현한 점,
두 번째 토큰을 세션 스토리지에 저장한 점

해결

AccessToken(실제 인가 필요 API 에 사용되는 토큰), RefreshToken(AccessToken 재발급에 사용되는 토큰), RefreshToken Rotation(RefreshToken 한번 사용 후 교체) 을 사용해서 더 안전하고 효율적인 통신을 할 수 있도록 구현했다.
먼저 기존의 JWT 기반 토큰은 AccessToken 으로 삼아 유효기간 1 시간 동일하게 설정해서 JWT 의 장점을 그대로 가져갔다. 프론트엔드와 협의해서 세션 스토리지가 아닌 자바스크립트의 변수로 저장하도록 했다. 이유는 아래와 같다. AccessToken 이 실제 인가와 관련된 모든 API 에 사용되기 때문에 안전한 곳에 저장되어야 한다. 로컬, 세션 스토리지는 XSS 공격에, 쿠키의 경우 CSRF 공격에 노출되기 쉽다. 하지만 토큰을 내부 변수로 저장함으로써 악성 스크립트(프론트엔드 코드를 보지않고는 토큰을 어디에 저장하는지 알 수 없다)자체를 만들기 어려우며(XSS 예방), 악성 사이트에 의한 불미스러운 요청을 막을 수 있다.(CSRF 예방, 다만 애초에 백엔드에서 계약된 프론트엔드의 Origin 만 허용하도록 설정했기 때문에 악성 사이트의 요청을 막는다)
RefreshToken 은 UUID 로 구현했다. 기존 JWT 기반 토큰의 단점인 서버에서 토큰을 컨트롤할 수 없다는 점을 해결하려고 했기 때문에 굳이 JWT 를 사용할 필요가 없었다.
RefreshToken 의 유효기간은 사용자 편의를 위해 14일로 설정했다.
RefreshToken 과 사용자 ID 를 서버에 저장해서 서버 측에서 토큰을 컨트롤 할 수 있도록 했다.
발급받은 RefreshToken 은 쿠키에 저장했다. 그 이유는 아래와 같다. 로컬, 세션 스토리지는 XSS 공격에 노출되기 쉬웠고 AccessToken 과 마찬가지로 JS 변수로 저장하면 페이지를 새로고침하거나 창을 닫는다면 저장된 내용이 사라진다. 즉 자동 로그인을 구현할 수 없다. 그래서 CSRF 공격에 취약할 수 있다는 점을 감안하고 쿠키에 저장했다.(CSRF 공격을 받더라도 RefreshToken 은 AccessToken 재발급에만 사용되기 때문에 별다른 피해는 없을 것이다, 그리고 공격자는 서버와 계약된 ORIGIN 이 아니기 때문에AccessToken 이 포함된 응답을 받을 수 없다) 또한 생?쿠키는 XSS 공격에 안전하지 않기 때문에 아래와 같이 httpOnly, secure 설정을 활성화했다.

private ResponseCookieBuilder createTokenCookieBuilder(final String value) {
        return ResponseCookie.from(REFRESH_TOKEN, value)
        
        		// 해당 쿠키는 자바스크립트로 접근할 수 없다.
                .httpOnly(true)
                
                // HTTPS 로 통신하는 경우에만 쿠키를 전송한다.
                .secure(true)
                .path("/")
                
                // 클라이언트측에서 Origin 과 Origin 에 링크된 사이트에만 쿠키를 전송한다.
                // 기본 설정이 SameSite.LAX (생략가능)
               .sameSite(SameSite.LAX.attributeValue());
}

이에 더해 RefreshToken 이 한 번 사용되면 폐기하도록 (RefreshToken 으로 새 AccessToken 을 발급 받을때 RefreshToken 도 새 RefreshToken 으로 발급한다) 해서 이미 사용된 RefreshToken 의 재사용(토큰 탈취)을 서버에서 인지할 수 있으며 이에 따른 후속 조치가 가능해진다.
이렇게 RefreshToken 을 구현함으로써 기존의 토큰 방식보다 안전하고 편리한 인가 방식을 제공 할 수 있었다. 또한 세션 방식과 비교해봐도 AccessToken 의 유효기간 동안은 DB 서버에 접근하지 않으므로 보다 효율적인 통신을 할 수 있다.

profile
꾸준하게 쌓아가자

0개의 댓글