지금까지 진행했었던 프로젝트에서 JWT 토큰을 발급하는 로직은 여러번 작성했었지만 그 토큰이 흐르는 전체의 로직은 구현해 본적이 없없다. 이번 기회에 로그인 로직전체를 구현해 보면서 전체의 흐름을 파악하는 기회를 가졌다.
OAuth2.0은 서드파티 어플리케이션이 제한된 접근 권한을 얻기 위한 인증 프레임워크 이다. AccessToken
과 RefreshToken
을 이용해 인증을 하는 역할을 한다. RFC 6749에 소개된 AccessToken
과 RefreshToken
을 사용하는 방식을 채택해 적용했다.
AccessToken
은 사용자의 아이디와 비밀번호가 맞는다면 그 사용자에게 접근 권한을 부여하는 역할을 가지고 RefreshToken
은 AccessToken
의 짧은 유효시간을 보완하는 역할로써 사용했다.
AccessToken
의 RefreshToken
을 활용한 인증 프로세스 및 는 아래와 같다.
(A~B) 사용자가 아이디와 패스워드를 통해 인증을 요청한다. 올바른 정보라면 AccessToken
과 RefreshToken
을 요청한다.
(C~F) 사용자가 자원을 요청할 때 AccessToken
을 같이 전송하게 하여 해당 자원에 대한 접근 권한이 있는지 확인하고, 있다면 자원을 제공하고, 그렇지 않다면 자원을 제공하지 않는다.
(G~H) AccessToken
이 만료되었다면 사용자가 가지고있는 RefreshToken
을 Authorization서버에 요청하여 올바른 RefreshToken
을 가지고 있다면 새로운 AccessToken
과 RefreshToken
을 반환한다.
모놀릭의 쓴맛을 봤었기 때문에 그 이후 MSA방식을 사용하려고 노력하고 있다. RFC 6749에서도 분리가 필수가 아니지만 인증서버와 자원서버를 분리하여 개발하기로 했다.
RFC 6749 내용
The authorization server may be the same server as the resource server or a separate entity. A single authorization server may issue access tokens accepted by multiple resource servers.
AccessToken
과 RefreshToken
저장/관리에 대한 고민이 부분은 서칭을 많이 하게 했던 고민이다. 처음엔RefreshToken
의 역할을 짧은 AccessToken
을 보완하는 역할과 AccessToekn
탈취시 보안 문제를 보완하기 위함으로 사용하는 것으로 주로 적혀있었고, 그렇다고 생각했었다. 하지만
AccessToekn
이 탈취가 가능하다면RefreshToken
도 똑같이 탈취가 가능하지 않는가❓ 그럼 똑같지 않은가❓
라는 생각으로 날 곤란하게 만들었다.😂
이는 각 토큰의 역할을 명확하게 정의하지 않아 생기는 혼돈이었고, 역할을 정확하게 정의하고 난다면 저장방식에 대한 고민을 해야하는 문제였다. 그래서 각 토큰의 역할 부터 다시 명확히 했다.
AccessToekn
은 사용자에게 접근 권한을 부여한다.
RefreshToken
은 사용자가 가진AccessToken
의 짧은 유효 시간을 보완하는 역할로 사용한다.
사용자에게 권한을 부여하지 않는다‼️ 단지AccessToken
을 재발급하는 것 뿐이다‼️
이렇게 역할을 다시 정리하고 나니 위의 똑같이 탈취가능하다 라는 부분을 해결하기 위해 두 토큰의 저장 혹은 관리 방식을 다르게 하면 해결할 수 있겠다 라는 흐름까지 올 수 있었다.
AccessToken
의 탈취 가능성을 보완하는 RefreshToken
이라면 두 토큰은 같은 방식으로 관리하면 안된다. 토큰을 저장 하는 방식은 아래와 같은 방법이 있었다.
쿠키에 refreshToken
만 저장하고 새로운 accessToken
을 받아와 인증에 이용하는 구조에서는 CSRF 취약점 공격을 방어할 수 있기 때문에 refreshToken
은 쿠키로, accessToken
은 localstorage에 저장하는 방식을 선택했다.
그 후 refreshToken
을 어떤 쿠키로 저장할지 선택해야 했는데 secure
쿠키는 https 프로토콜이 필요했으나 아직 https를 적용하지 않았고, httpOnly
쿠키는 자바스크립트로 접근이 불가능한 반면에 일반 쿠키는 접근이 가능했으므로 httpOnly
쿠키를 사용하기로 했다.
인증서버와 자원서버가 분리됨에 따라서 프론트에서 요청을 두 곳으로 적절하게 보낼 수 있어야했다.
물론 각 요청마다 주소를 전부 적어주면 가능한 일이지만 그렇게 중복되고 보기 싫은 코드는 작성하고 싶지 않았다.
그래서 Axios instance
를 사용해서 인증서버에 요청을 보내는 인스턴스, 자원서버에 요청을 보내는 인스턴스를 생성해서 사용하는 방법을 선택했다.
풀스택으로 개발을 진행해야 하다 보니 토큰을 단순 발급/관리 하는 고민 뿐만아니라 사용자의 경험까지도 고민했어야 했다. 그리고 고민과정 중에서 사용자가 웹서비스를 사용할 때에 토큰이 만료됨을 눈치 채지 못하고 사용이 가능했으면 좋겠다!
라는 생각이 있었다.
그에 따라 사용자가 서비스 사용시 토큰의 상태를 나열해 보았다.
AccessToken
, RefreshToken
모두 문제 없는경우AccessToken
만 만료 된경우Accesstoken
은 없고, RefreshToken
만 존재하는 경우이 부분은 프론트엔드에서의 모든 요청에 적용되는 공통되는 내용이므로 이전 고민에서 선택하기로한 Axios instance
를 활용해서 처리할 수 있는 방법이 있는지 서칭했다. 이전 Spring boot 프로젝트를 진행할 때 interceptor
를 활용한적이 있어서 axios도 interceptor
가 있는지 봤고, 제공하고있었다!
request, response interceptor를 제공하고 있어서 요청 전, 요청 후 처리를 가능하게 했다.
따라서 axios interceptor
를 이용해 각 경우에 대해 예외 처리도 가능하게 했다.
RefreshToken
이 쿠키 형태로 발급됨에 따라서 브라우저에 저장이 될꺼고, 해당 토큰이 만료되지 않았다면 사용자는 사이트를 나간 후 다시 접속을 해도 인터셉터를 통해 새로운 AccessToken
을 요청할 것이고, 사용자는 로그인 과정없이 계속 사이트를 이용할 수 있게된다. 사용자가 로그아웃을 요청하고 사이트를 떠났다면 다시 로그인을 하기 전까진 사이트를 이용할 수 없어야한다. 하지만 RefreshToken
을 httpOnly cookie 방식을 사용했고, 이 방식은 코드단에서 쿠키에 접근할 수 없었다. 따라서 RefreshToken
에 대한 정보를 인증서버에서 관리하고 있어야했다. 따라서 RefreshToken
에 대한 정보는 인증서버에서 관리할 수 있도록 설계해서 사용자가 로그아웃 요청시 해당 토큰은 브라우저에 남아있겠지만 인증서버에 만료되었다는 정보를 유지하게되어 사용자가 다시 로그인 시도를 하기 전까진 서비스를 사용못하도록 할 수 있었다.
다음편 안올려주시나요 ㅠㅠ