
사용자 맞춤형 서비스는 현대 웹 서비스에서 흔하게 요구된다. 이때 웹 서버가 사용자를 구별하기 위한 방법이 로그인이다. 로그인부터 시작해서 Session 인증, JWT인증이 생겨난 이유와 각 인증 방식의 특징까지 고민했던 내용들을 정리해보자.
매순간 로그인을 하는 것은 굉장히 번거로운 일이다. HTTP 통신의 Stateless한 특성을 살린다면 모든 HTTP 요청마다 로그인 정보를 함께 담아서 보내야한다. 이때 ID와 Password를 매순간 담아서 보낸다면? 해커에 의해서 탈취 당하기 쉬우며, 굉장히 번거로울 것이다.
HTTP의 Stateless 아키텍처는 서버가 클라이언트의 상태를 저장하지 않는 것을 말한다. HTTP는 이론적으로 완벽하게 Stateless하게 설계되어야 하지만 앞서 번거로운 상황을 막기 위해서 실제 웹 서비스는 여러가지 저장소를 사용할 수 있다. 브라우저 저장소는 Cookie와 Web Storage(Local Storage, Session Storage)를 가지며 서버 저장소로는 Session이 있다. 이런 저장소를 활용한 대표적 인증방법들이 바로 Session인증, JWT인증 방식이다.
각 저장소에 대한 특성은 다른 포스팅에서 정리하겠다.

출처: http://gcs.emro.co.kr:8090/pages/viewpage.action?pageId=9873130
Session-based authentication is a stateful authentication technique where we use sessions to keep track of the authenticated user. -roadmap.sh
JWT보다 역사적으로 오래된 인증방식이며 Django나 Spring Security에서 기본적으로 제공하는 인증방식이다.
장점
단점
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. - jwt.io
JWT은 위와 같이 HEADER, PAYLOAD, SIGNATURE로 구성된 JSON이다. HEADER에는 토큰의 타입과 암호화 알고리즘의 종류, PAYLOAD에는 인증정보와 토큰의 유효기간, SIGNATURE에는 'HEADER.PAYLOAD'를 암호화 알고리즘에 따라 비밀키로 암호화한 값이 들어간다. 이를 BASE64방식으로 인코딩 해주면 왼쪽과 같은 JWT이 만들어진다. 구체적인 구성 정보들은 공식 사이트를 들어가보면 확인 할 수 있다.

출처: http://gcs.emro.co.kr:8090/pages/viewpage.action?pageId=9873130
JWT인증방식은 세션ID 대신 JWT을 클라이언트에 전달하며, 서버는 나중에 쿠키에 담겨온 요청을 해독하여 PAYLOAD 내부의 인증정보를 바탕으로 사용자를 식별한다. 즉 세션 저장소를 사용하지 않으며 HTTP의 stateless한 설계를 가져올 수 있는 방식으로 개발되었다.
이때 HEADER와 PAYLOAD는 암호화 없이 BASE64방식으로 인코딩만 되어있기에 해커가 쉽게 탈취 후 조작이 가능한데, 서버 측에서 SIGNATURE 부분을 비밀키로 복호화 하면 똑같은 HEADER PAYLOAD가 또 들어가 있기에 두 값이 다르면 해커에게 조작당했음을 알 수 있다.

https://datatracker.ietf.org/doc/html/rfc6749
https://oauth.net/
OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다 - 위키백과
Refresh Token은 사실 웹 인증 표준 프로토콜 중 하나인 OAuth로부터 나온 것이다. 소셜로그인 서비스가 대부분 OAuth 표준을 지켜서 만들어졌으며, OAuth는 Access Token과 Refresh Token으로 분리되며 이런 토큰들을 활용한 인증과정에 대해서 다루고 있다. 이때 Access Token으로 구현될 수 있는 것 중 하나가 JWT이다. 또한, JWT은 이와 별개의 토큰 기반 웹 인증 표준 중 하나이다. 즉 JWT과 OAuth(Access Token & Refresh Token)은 아예 별개의 표준이며 OAuth 프로토콜을 구현함에 있어 Access Token을 JWT으로 구현할 수도 있다고 이해하면 되겠다.

출처: https://is.docs.wso2.com/en/5.9.0/learn/refresh-token-grant/
기존의 JWT 인증방식은 탈취 당하면 관리가 사실상 불가능하기에 유효기간을 제한한다. 그러나 해당 유효기간을 너무 짧게 잡으면 사용자가 직접 로그인을 자주해야하며, 너무 길게하면 보안적으로 문제가 발생한다. 이런 문제를 해결 할 수 있는 것이 JWT을 다시 발급하기 위한 토큰 Refresh Token이다. 다시한번 강조하지만 Refresh Token은 웹 표준 인증 프로토콜 중 하나인 OAuth에서 등장한ㄲ 개념이다.
Refresh Token은 JWT으로 구현될 수도 있으며 랜덤한 문자열이 될 수 있으며 정해진건 없다. 이는 Access Token도 마찬가지이다. 단지 OAuth에서는 각 역할을 명시하고 있다. 그러나 일반적으로 Refresh Token은 UUID를, Access Token은 JWT을 사용한다. 위의 그림으로 흐름을 파악해보자. (OAuth에서 제공하는 RFC 문서의 Refresh Token 부분을 가져온 것이다.)
이렇게 구현하면 사용자 입장에서는 마치 인증이 단순히 계속 유지되는 것으로 보이지만, 보안이 취약한 Access Token의 유효기간은 최소화 할 수 있다.
OAuth에서는 Refresh Token이 String이며 동일하거나 그보다 적은 권한의 AccessToken을 재발급받기 위한 용도라는 것을 명시하고 있다. 따라서 구체적으로 정해진 것은 없으며 우리는 기본적으로 UUID(랜덤한 문자열)과 JWT을 고려할 수 있다.
대부분 권장하는 구현방법이다. 서버 DB에 사용자 식별자와 UUID(옵션으로 Access Token까지)를 저장해서 관리해야 하며, Redis를 자주 활용한다.
JWT + Refresh Token 해킹 시나리오
각 토큰이 탈취 당했을때 일어나는 일을 생각해보자
이 외에 보안 강화하는 전략으로 생각해볼만한건 아래와 같다.
Session 인증과의 유사성
지금까지 살펴봤다면 알겠지만 Refresh Token을 UUID를 활용해 구현하면 일종의 세션인증방식과 동일하며, 따라서 관리가 가능하지만 DB를 조회하는 시간이 걸린다는 장단점 또한 동일하게 가져온다. 즉 JWT + Refresh Token은 세션인증과 JWT인증방식의 하이브리드라고 보는게 맞을 듯 하다. 또한 Stateless한 아키텍쳐에서 오는 스케일 아웃의 편리함과 같은 장점은 아예 사라진다.
요약
장점: 세션 인증과 마찬가지로 관리가 가능하다.
단점: Stateless 설계 파괴
즉 JWT과 Session 인증의 하이브리드 느낌으로 볼 수 있다.
대신 Session 인증보다 Session 저장소 접근 횟수가 적다. (부하 감소)
일반적으로 해당 방법은 권장하지 않는다. 만약 세션 저장소 없이 JWT과 동일하게 관리하지 않고 무상태성 JWT을 사용한다면, Refresh Token을 일회성으로 제한하기 어렵다.
그렇게 되면 단순히 수명이 긴 Access Token이 된다.
Access Token : 로컬 스토리지
이유 : 인증이 가능하다는 특징이 있어 CSRF공격의 위험성이 있다.
또한 수명이 짧아 XSS에는 피해가 최소화된다.
자주 갱신해야하는데 HttpOnly 쿠키는 다소 관리가 번거롭다.
Refresh Token : HttpOnly 쿠키
이유 : 수명이 길어 민감한 데이터이므로 XSS로 접근 불가능 하도록 한다.
인증에는 영향이 없기 때문에 CSRF로부터 상대적으로 안전하다.
여기부터는 정리해본 내가 프로젝트에서 어떤 인증방식을 적용할지에 대한 내용이다.
소셜 로그인은 웹표준으로 AOuth 프로토콜을 사용하고 있으니, 만약 사용하고 싶다면 AOuth 프로토콜에 맞춰 적용하면 된다.
그러나 나의 서비스에서 자체적으로 인증을 처리하는 경우 세션을 사용하는 것이 유리해보인다. 그 이유는 다음과 같다.
JWT authentication is inherently less secure than a plain old session, and should only be used if there is a need. Many times when token auth is used, it is not actually necessary, just fancy.
Fortunately, very few applications actually need to be truly stateless on the server-side.
https://stackoverflow.com/questions/69800098/whats-the-whole-point-of-a-jwt-refresh-token
해당 스택오버플로 답변을 인용한 것이다.
요약하자면 이렇다, "JWT인증 방식은 보안 문제로 인해 반드시 필요한 곳에 사용해야 합니다. 그러나 다행히도 완벽히 서버측의 비상태성을 요구하는 어플리케이션은 거의 없습니다."
https://velog.io/@park2348190/JWT%EC%97%90%EC%84%9C-Refresh-Token%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80
https://stackoverflow.com/questions/27726066/jwt-refresh-token-flow
https://stackoverflow.com/questions/69800098/whats-the-whole-point-of-a-jwt-refresh-token
너무 정리를 잘 하시네요 잘 보고 갑니다 ㅎ