
프로젝트를 진행하며 JWT를 활용한 인증/인가 로직 구현은 이번이 처음이다.
️🌈 JWT를 도임하게된 이유 :
이전 모놀리식 방식의 프로젝트와는 다르게 이번 프로젝트는 MSA 구조를 모방한 형태의 프로젝트이다. 즉, 프로젝트의 관심사를 여러개로 나누어 각 서비스마다 서버를 하나씩 두는 구조로 진행 중이다.
위와 같은 구조로 프로젝트를 진행하면서, 기존의 session을 사용한 인증/인가 방식을 적용하는 것이 가능할 지 의문이 들었다.
결로부터 말하면, MSA 구조에서는 토큰 기반 인증이 좀 더 용이하며, session을 사용하는 것이 쉬운일은 아니기 때문이다.
MSA 구조는 서버가 MS마다 분리된 구조이며, session도 각 서버마다 다르다. 이러한 이유로, 하나의 서버에서 인증/인가 과정을 거친 클라이언트가 다른 서버의 서비스를 요청할 경우, 이 인증 정보를 공유할 수 없게되어 다시 인증/인가를 거쳐야 되는 일이 발생할 수 있다.
따라서 이러한 이유로 jwt 라는 토큰을 기반으로한 인증/인가 방식을 도입하게 되었다.
️🌈 본론으로 돌아와서,
현재 로그인 후 토큰급 및 access-token과 refresh-token을 전달해주는 곳까지 구현이 완료되었다.
access-token은 쿠키에 담아서 브라우저 쿠키 저장소에 저장되도록 구현하였으며, refresh-token은 Authorization 헤더에 담아서 전달하도록 구현하엿다.
access-token으로 인증/인가를 진행하며, access-token이 만료된 경우 refresh-token을 함께 가져와 access-token 재발급하는 방식으로 구현하였다.
하지만, 의문이 드는 점이 있었다.
❓ refresh-token을 어디에 저장해둬야 하는지 안전한지 의문이 들었다.❓
웹 브라우저 내 저장할 수 있는 저장소는 크게 3 곳이다.
🫙 로컬 스토리지,
🫙 세션 스토리지,
🫙 쿠키 저장소
🏳️🌈 로컬 스토리지와 세션 스토리지의 차이점 :
데이터의 보관 주기이다.
로컬 스토리지 - 영구 저장,
세션 스토리지 - 일시 저장
하지만, 두 스토리지에는 치명적인 단점이 있는데, 이는 XSS(Cross Site Scripting) 공격에 취약하다는 점이다.
사이트 간 스크립팅으로, 이는 웹사이트 관리자가 아닌 다른 사람이 웹 페이지에, 악성 스크립트를 삽입할 수 있다는 취약점에 의해 발생한다.
즉, 공격자가 상대방의 브라우저에 악성 스크립트를 삽입하여, 사용자의 세션이나 쿠키를 탈취하거나 웹사이트를 변조하여 악의적인 동작을 수행하도록 하는 보안 공격이다.
두 스토리지는 모두 자바스크립트로 데이터를 저장하거나 꺼낼 수 있기 때문에, XSS 공격에 취약하게 된다. 따라서, 스토리지에 민감 정보를 저장하는 것은 위험하다.
🏳️🌈 쿠키 저장소는 괜찮을까..?
그 다음으로 쿠키가 있는데, 쿠키는 만료 기한이 있는 키-값 저장소이다. 쿠키 또한 자바스크립트로 접근이 가능하지만, HTTP ONLY 옵션을 설정하여 자바 스크립트로 접근하는 것을 방지할 수 있다.
하지만 쿠키에 토큰을 담게 되면 CSRF(Cross Site Request Forgery) 공격에 취약해진다.
사이트 간 요청 위조로 웹사이트 취약점 공격 중 하나이다.
사용자가 자신의 의지와는 무관하게 공격자가 의도한 요청을 특정 웹사이트에 요청하게 하는 공격을 말한다. 이는 사용자가 정상적으로 웹사이트에 로그인을 한 상태에서 공격을 받게되는 보안 공격이다.
CSRF의 경우 어느정도 방어적인 측면에서 상쇄가 가능하다.
🏳️🌈 그렇다면, 어디에 저장하는 것이 가장 안전할까??
🎆 Refresh Token의 값을 DB에 저장하고, 클라이언트에 해당 uuid 또는 idnex 값을 쿠키나 로컬 스토리지에 저장하는 것이 가장 안전하다.
쿠키에 구분값을 저장하는 경우, 만료 기간을 길게 잡아 끊임없는 서비스 이용이 가능하도록 할 수 있다.
하지만, DB에 저장하게 되면, JWT를 사용하는 의미가 없어지게 된다는 의견이 많다. 즉, JWT를 사용하는 이유가 토큰 내 시그니처로 자체 검증이 가능하기 때문이지만, DB에 저장하여 사용할 경우, 이 장점이 사라지게 된다.
따라서, 이번 프로젝트에서는 access-token과 refresh-token 둘 다 Http-only의 쿠키에 담아 저장하는 방식으로 구현할 예정이며, RTR 방식을 도입하여, Access-Token 갱신 시마다, Refresh-Token도 갱신되도록 로직을 수정할 예정이다.
추가로 해당 기능이 완성되면, 보안적인 측면을 보완하기 위해 만료된 access-token을 black-list에 등록 되도록 리팩토링 해봐야 겠다.
🏳️🌈 그럼에도 불가하고, 토큰이 탈취되는 불상사가 발생한다면..???
이를 방지하기 위한 조치로 각 토큰마다 있다.
Refresh-token 탈취를 막기위한 방식으로, RTR(Refresh Token Rotation)이 존재한다. 이는 Refresh Token을 한번만 사용하도록 만드는 방법이다. 즉, Refresh Token을 사용하여 새로운 Access Token을 발급 받을 경우, Refresh Token도 새롭게 발급받도록 하는 것이다.
access-token 탈취를 막기 위한 방식으로, 로그아웃 한 경우 Blacklist를 사용하면 된다.
Blacklist는 RedisTemplate의 기능 중 일부로, 재발급 된 뒤 이전 access-token을 등록하면, 다음의 인증/인가 시, 해당 access-token으로 접근이 불가능하도록 막을 수 있다.
참고 포스팅
토큰을 어디에 저장해야 할까?