그래서 jwt, Access, Refresh Token 이 뭔데?
이전 글 안드로이드 레트로핏 api 호출하는 인터페이스에서 @Header 중복을 제거하기 에서 Access token 을 @Header 에 넣는 부분에 대해서 다뤘습니다.
그렇다면 Access Token 이 대체 무엇일까요?
Access Token 이전에 Jwt 토큰을 알아야 합니다.
하나 하나 차근차근 알아봅시다.
JWT 토큰이 뭔데?
JWT(Json Web Token) 은 웹에서 사용되는 JSON 형식의 토큰에 대한 표준 규격입니다. 유저의 권한을 결정하는 정보를 담는 데이터 조각으로 유저의 인증(Authentication), 인가(Authorization) 정보를 담습니다.
Jwt 토큰을 사용해서 클라이언트와 서버는 안전하게 통신할 수 있습니다.
클라이언트에서 jwt 토큰을 담아서 서버에 정보를 보내면 서버에서 그 토큰 정보를 통해 정상적인 사용자인지를 판별하고 인증을 해줍니다.
그런데 이 토큰을 중간에 누군가가 탈취하면 문제가 되겠죠? jwt 토큰을 탈취한 사람은 신뢰할 수 있는 사람처럼 서버의 인증을 받을 수 있게 됩니다. jwt 는 stateless 한 방식이기 때문에 서버 측에서는 이 토큰을 가지고 있는 클라이언트가 정상적인 사용자 본인이 맞는지 확인할 수 없다는 것이 문제입니다. 즉, 서버가 토큰 탈취자와 정상 사용자를 구분할 수 없게 됩니다.
jwt 토큰을 짧은 유효기간을 두어서 갱신되게 만들면?
jwt 토큰이 짧은 시간 동안만 유효하게 하고 이 시간이 지나면 사용자가 새로운 토큰을 발급받아 새로 인증을 하게 하면 어떨까요?
그렇게 되면 탈취자가 토큰을 탈취하더라도 금방 유효기간이 만료되기 때문에 계속해서 잘못된 접근을 할 수 없게 됩니다.
하지만 정상적인 사용자 또한 짧은 시간 후에 계속해서 토큰을 새로 발급받아야 해서 로그인을 다시 해야 하는 문제가 있습니다. 사용자 경험 측면에서 별로인 것이죠.
jwt 토큰을 긴 유효기간을 두어서 갱신되게 만들면?
그렇게 되면 사용자 경험의 측면에서는 좋을 겁니다. 하지만 그만큼 탈취자가 토큰을 탈취했을 때 긴 기간동안 부적절한 접근을 할 수 있게 되겠죠.
보안 측면에서 별로이게 됩니다.
유효기간이 다른 jwt 토큰 2개 - Access token, Refresh token
그래서 보통 유효기간이 다른 jwt 토큰을 2개 두어서 사용합니다.
유효기간이 짧은 토큰이 Access token 이고, 유효기간이 긴 토큰이 Refresh 토큰입니다.
평소 api 통신을 할 때는 Access token 을 사용하고 Refresh token 은 Access token 이 만료되어서 갱신해야 할 때만 사용합니다.
통신 과정에서 자주 송,수신 되어서 탈취당할 위험이 큰 Access token 은 만료기간을 짧게 두고, 만료되면 Refresh token 으로 엑세스 토큰을 주기적으로 재발급하는 것이죠. 사용자 경험 측면, 보안 측면에서 나쁘지 않은 선택이죠.
Access Token 과 Refresh Token 원리 그림으로 보기
엑세스 토큰과 리프레시 토큰을 사용하여 인증을 하는 방식을 단순하게 표현한 그림입니다.
위 그림에 따라 엑세스 토큰이 탈취되어도 엑세스 토큰의 만료 기간이 짧기 때문에 탈취자는 다시 탈취를 시도해야 합니다.
Access 토큰 탈취 문제
🙋 그런데 탈취자가 Access token 을 탈취하고 나서
그 토큰의 만료 기간을 길게 조작해서 서버에 계속 접근하면 되는거 아닌가요?
사실 JWT 토큰의 만료 기간을 수정하는 것은 쉬운 일이 아닙니다.
이에 대해서는 먼저 JWT 토큰의 구조를 이해할 필요가 있습니다.
JWT 토큰의 구조
JWT 토큰의 구조는 헤더(Header), 페이로드(Payload), 시그니처(Signature) 로 이루어져 있습니다.
(위 클라이언트와 서버가 jwt 토큰을 가지고 통신하는 모습에서의 Header, Payload(Data) 가 아니라 하나의 토큰 자체가 이렇게 이루어져 있는 것임.)
만료 기간은 페이로드에 담겨 있습니다. 시그니처는 헤더와 페이로드를 비밀키(개인키 or 대칭키)로 암호화한 것을 담고 있죠.
탈취자가 만약 페이로드에 있는 만료 기간을 늘린다고 해도 시그니처는 바뀌지 않습니다.
서버는 클라이언트에서 보낸 토큰의 시그니처 안에 암호화되어 있던 내용을 가지고 있던 비밀키로 디코딩(복호화)해서 페이로드를 얻습니다. 그리고 토큰의 페이로드와 시그니처에서 얻은 페이로드를 비교하죠. 만약 이게 다르면 서버는 접근 권한을 내어주지 않습니다.
그래서 탈취자가 Access token 을 탈취해서 토큰 만료 기간을 수정하더라도 서버에서 인증을 해주지 않습니다.
그럼 다른 의문점이 생길 수도 있습니다.
🙋 탈취자가 Refresh 토큰을 탈취해버리면????
Refresh 토큰 탈취 문제
Refresh 토큰은 Access 토큰의 만료 기간이 끝났을 때 클라이언트가 서버로 보내기 때문에 통신 빈도가 적죠. 하지만 그렇다고 해도 탈취될 수 있습니다.
만약 탈취자가 Refresh 토큰을 탈취해서 긴 유효기간동안 Access 토큰을 생성해서 사용할 수도 있지 않을까요?
이 문제에 대해서는 서버 측에서의 검증 로직이 필요하며, 여러 시나리오가 있을 수 있습니다. 천천히 살펴봅시다.
Refresh 토큰이 탈취, Access 토큰 만료 전 탈취자가 요청하는 경우
서버 측에서 Refresh 토큰에 대응하는 Access 토큰이 만료되었는지를 판단하고, 만료되지 않았다면 Access 토큰과 Refresh 토큰을 만료시켜버립니다.
이 경우 간단하게 문제 해결이 가능합니다.
하지만 여전히 문제는 존재하죠.
Access 토큰이 만료된 후에 탈취자가 Refresh 토큰으로 Access 토큰 재발급을 요청할 수도 있습니다..
Refresh 토큰이 탈취, Access 토큰 만료 후 요청하는 경우 (정상 사용자가 먼저 요청)
이 경우 OAuth 는 Refresh Token Rotation(RTR) 이라는 기술을 제시합니다.
아예 Refresh Token 도 Access Token 과 같은 유효 기간을 가지도록 하여
사용자가 한 번 Refresh Token으로 Access Token을 발급받았으면 Refresh Token도 다시 발급받도록 하는 것입니다.
이렇게 되면 탈취자가 Refresh Token 으로 Access token 으로 오랫동안 계속 부적절한 접근을 할수 없어집니다.
Refresh 토큰이 탈취, Access 토큰 만료 후 요청하는 경우 (탈취자가 먼저 요청)
이 경우에는 문제가 좀 복잡해 집니다.
이번에는 문제 상황을 먼저 그림으로 봅시다.
여기서는 크게 두가지 문제점이 보입니다. 문제점을 Ⓐ 와 Ⓑ 라고 하겠습니다.
첫번째 문제 Ⓐ
탈취자가 정상 사용자보다 먼저 Refresh 토큰으로 Access 토큰으로 접근을 하는 경우에 서버와 통신하는 것을 막을 수 없습니다.
두번째 문제 Ⓑ
이후에 정상 사용자가 갱신되기 전 Refresh 토큰(그림에서 Refresh Token(Old) )로 Access 토큰을 요청해도 인증할 수 없어, 다시 로그인을 해야 하고 이 때 서버에 있는 토큰 DB 에는 같은 사용자에 대해 두 쌍의 Access token, Refresh token 이 생깁니다.
한 사용자가 여러 토큰으로 서버와 통신을 할 수 있게 되는 것이죠...
이에 대해서는 아래처럼 해결하면 됩니다.
RTR 사용, 서버의 토큰 DB 에 PK 설정해서 해결하기
서버의 DB 토큰 관련 스키마에서 사용자 칼럼을 PK 로 설정하는 것입니다.
그리고 UserPK 에 대응되는 Refresh 토큰은 오직 1개만 가지도록 합니다.
만약 위 상황에서처럼 탈취자가 먼저 Refresh 토큰으로 Access 토큰을 얻고 나중에 정상 사용자가 Refresh 토큰으로 Access 토큰을 요청한다고 하는 상황이라고 합시다.
그렇다면 아래와 같이 작동됩니다.
클라이언트에서 Refresh 토큰을 사용하여 Access 토큰 재발급을 요청할 때 서버에서는 수신한 Refresh 토큰에서 UserPK 를 알아내고 이에 대응되는 Refresh 토큰을 검색합니다.
서버 DB 에서는 탈취자에 의해 먼저 재발급된 Refresh 토큰이 정상 유저가 가지고 있떤 Refresh 토큰과 다르다는 것을 확인할 수 있습니다.
이 때 서버에서는 악의적인 침투가 있다는 것을 알아내고 해당 유저에 대한 토큰 데이터를 삭제하고 재로그인하도록 하는 것입니다. 그러면 탈취자가 얻은 토큰으로는 아무것도 할 수 없게 되는 것이죠.
이렇게 RTR 을 사용하면서 DB 에 저장 방식을 위처럼 적용하면, 이전에 Ⓐ 와 Ⓑ 문제점을 해결할 수 있습니다.
그렇다면 이제 모든 보안 문제가 해결된 것일까요???
불행하게도 그렇지 않습니다.... 사실 바로 알 수 있을 겁니다.
여전히 문제가 되는 부분이 있다
만약 바로 위 그림처럼 정상 유저가 Refresh 토큰을 탈취자에게 탈취당하고 탈취자가 이를 이용해서 Access 토큰을 재발급받으면서 사용하고 있다고 합시다.
그리고 정상 사용자는 어떠한 이유로 해당 서비스를 오랫동안 사용하지 않는 상황입니다.
그렇다면 정상 사용자가 다시 로그인할 때까지 탈취자는 굉장히 오랫동안 부적절한 접근을 할 수 있는 것입니다..
이것은 Stateless 한 토큰 기반 인증 방식을 사용할 때의 불가피한 문제로 볼 수 있습니다. 물론 이 문제를 해결하는 방법도 또 여러 가지가 있겠죠... 하지만 유명한 말이 있습니다.
But then again there is nothing like 100% security.
결국 100 % 안전한 보안은 없습니다.
프론트엔드나 백엔드 로직을 강화하여 토큰이 유출되지 않도록 보완하는 수 밖에 없겠죠..
정리
- JWT 토큰은 Stateless 한 클라이언트-서버 인증 방법
- Refresh 토큰과 Access 토큰으로 나누어서 사용한다.
- RTR 과 서버 측의 추가 검증 로직을 사용하여 어느 정도 보안의 결점을 보완할 수 있다.
- 하지만 완벽한 보안은 없다.
이렇게 Jwt 토큰에 대해서 간단히 알아보았습니다. 시나리오를 하나씩 가정해가면셔 공부하니까 재미 있네요~ ㅎ
참고한 사이트
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them