안녕!
우아한테크코스 5기 [스탬프크러쉬]팀 깃짱이라고 합니다.
사장모드: stampcrush.site/admin
고객모드: stampcrush.site
💋 인트로
스탬프크러쉬는 사용자의 인증(로그인)을 구현하기 위한 방식으로 JWT(Json Web Token)를 사용한 방식을 택했다.
이번 포스팅에서는 JWT 방식을 사용한 로그아웃, Refresh Token을 통한 Access Token 재발급 방식에 대해 포스팅해보려고 한다.
💋 JWT 방식
✔️ JWT 방식은 원래 Stateless
하다.
JWT는 세션을 사용한 방식과 달리, Stateless
한 특징을 최대한 살린 형태로 동작한다.
여기서 순정 JWT라고 설명한 이유는, 이후에 소개할 방식들은 JWT의 방식에 Stateful
한 방식을 결합한 형태로 문제를 해결하기 때문이다.
참고로, 데이터베이스로부터 사용자의 상태를 조회하는 것은 일반적으로 Stateful
하다고 표현한다. Stateful
은 상태를 유지하고 관리하는 방식을 의미하며, 사용자의 로그인 상태를 데이터베이스에 저장하고 해당 상태를 조회하는 경우에 Stateful
하다고 볼 수 있다.
JWT가 Stateless한 것은 최초의 로그인 성공 시와 로그인 성공 이후의 모든 요청 두 가지 경우를 세션 방식과 비교해보면 쉽게 파악할 수 있다.
- 최초의 로그인 성공 시
- 세션 방식에서 서버는 세션 DB에 사용자의 정보를 저장하고, 생성된 세션 ID를 (일반적으로) 쿠키에 넣어서 클라이언트에 반환한다.
- JWT 방식에서 서버는 사용자의 정보 가운데 민감 정보가 아닌 정보로 JWT 알고리즘을 굴려서 토큰을 생성해 클라이언트에 반환한다.
- 로그인 성공 이후의 모든 요청 시
- 세션 방식에서 클라이언트는 세션 ID를 포함한 쿠키를 서버에 전달하고, 서버는 쿠키에서 세션 ID를 꺼내, 세션 DB에 해당 아이디에 해당하는 레코드가 존재하는지 조회하고, 존재하지 않으면 로그인이 되어있지 않다고 판단한다.
- JWT 방식에서 서버는 서명 부분을 확인해 JWT 알고리즘을 굴려서 해당 토큰이 조작되었는지 판별하고, 조작되지 않았다면 JWT 알고리즘을 굴려 토큰으로부터 사용자의 아이디와 같이 식별할 수 있는 정보를 꺼내서 사용한다.
위의 설명에서 JWT 방식은 단 한 번도 데이터베이스에 저장한다거나, 조회한다는 과정이 포함되지 않는 것을 볼 수 있다.
= JWT 방식은 Stateless
하다고 볼 수 있다.
나는 이렇게 데이터베이스를 전혀 사용하지 않는 완전히 Stateless
한 방식을 순정 JWT
방식이라고 부르도록 하겠다.
✔️ 순정 JWT 방식으로는 안전한 로그아웃을 구현할 수 없다.
JWT(JSON Web Token) 방식은 서버 측에서 사용자의 로그인 상태를 강제할 수 없다.
서버는 토큰을 발급하면, 그 뒤로는 토큰에 대한 제어를 완전히 잃어버린다. 어딘가에 내가 발급한 토큰도, 어떤 클라이언트에 토큰을 발급했다는 정보도 저장해두지 않기 때문이다.
JWT가 토큰 자체에 사용자 정보
와 만료 시간
을 포함하고 있으며, 서버 측에서 이를 확인하여 인증하는 방식이기 때문이다.
데이터베이스를 전혀 사용하지 않는 순정 JWT 방식을 사용할 때, 로그아웃은 클라이언트 측에서 쿠키나 로컬 스토리지에서 JWT 토큰을 삭제하거나 무효화하는 방식으로 구현할 수 있다.
하지만, 이 경우 악성 해커가 토큰을 탈취해서 사용한다면, 서버는 토큰 만료 시간까지는 더이상 손쓸 방법이 없다. 혹시 Refresh Token도 함께 탈취당한 경우에는 정말 영원히 악성 해커를 막을 수 없게 될 수도 있다.
따라서, 데이터베이스를 전혀 사용하지 않는 순정 JWT 방식으로는 안전한 로그아웃을 구현할 수 없다.
✔️ 순정 JWT 방식으로는 Refresh Token을 통한 Access Token 재발급을 구현할 수 없다.
일반적으로 Refresh Token은 Access Token의 만료 시간이 지난 후에 새로운 Access Token을 발급받기 위해 사용된다. 이 과정에서 Refresh Token은 서버에 저장되어야 하며, 유효성을 검증하고 새로운 Access Token을 발급하는 작업이 필요하다.
서버 측에서 Refresh Token을 저장해야 하기 때문에 데이터베이스를 전혀 사용하지 않고서는 Refresh Token을 통한 Access Token 재발급을 구현할 수 없다.
이 경우, Access Token이 만료되면 사용자는 로그아웃 상태가 되고, 새로운 로그인을 해야만 하기 때문에, Access Token의 만료 기간이 짧은 경우에는 매우 안좋은 사용자 경험을 줄 수 있다.
그렇다고 Access Token의 만료 기간을 길게 잡게 되면, 보안이 매우 취약해져 악성 해커가 아주 좋아하는 서비스를 만들 수 있다.
💋 JWT 방식 + 데이터베이스 사용 (한 스푼의 Stateful
)
완전한 순정 JWT를 포기하고, 일부분 데이터베이스의 힘을 빌리기로 타협한다면, 보안도, 사용자의 경험도 개선할 수 있다.
✔️ 로그아웃 시에 블랙리스트에 Refresh Token을 저장한다.
우리 팀이 가장 합리적이라고 생각해 선택한 방법이다.
Access Token의 만료 기간을 30분 이하로 아주 짧게 설정한다.
만료 시간이 굉장히 짧기 때문에, Access Token이 탈취되더라도 피해를 최소화할 수 있다.
로그아웃한 사용자의 Refresh Token은 블랙리스트의 역할을 하는 데이터베이스에 저장한다.
블랙리스트는 쉽게 생각해서 만료되거나 무효화(로그아웃)된 토큰을 저장하는 테이블이다.
이 방식을 사용할 경우에, 클라이언트가 로그아웃 할 경우에 프론트엔드 코드에서 직접 Access Token을 삭제해주면 된다. 만약 Access Token이 탈취되었더라도 만료 기간을 짧게 설정해 두었기 때문에 비교적 안전하다.
Access Token이 만료되어 Refresh Token으로 새로운 Access Token을 요청하는 경우에도, 서버는 블랙리스트에 해당 Refresh Token이 저장되어 있는 것을 확인하고, 해당 Refresh Token은 로그아웃된 유저라는 것을 알 수 있기 때문에 Access Token 재발급을 거절할 수 있다.
조금 stateful
하지만, 악성 해커가 계속해서 판을 치는 것을 방지하면서도, Access Token이 만료되었을 때 Refresh Token을 통해 재발급을 받아 너무 자주 로그인이 풀리는 것을 방지해서 사용자 경험도 향상될 수 있다.
서버 측에서는 블랙리스트를 관리하여 무효화된 토큰을 거부할 수 있다.
물론 여전히 Access Token이 탈취당한 경우에는 토큰이 만료되기까지 기다릴 수밖에 없다는 점은 이전과 동일하다. 하지만, Refresh Token 방식을 사용할 수 있게 되어, 부담없이 Access Token의 만료 기간을 매우 짧게 설정할 수 있어서 보안 측면에서는 확실히 개선된다.
✔️ 로그아웃 시에 블랙리스트에 Access Token도 저장한다.
위의 방식에서는 Access Token이 탈취당했을 때, 서버 측에서 해당 토큰을 제어할 수 없기 때문에 토큰이 만료될 때까지 기다리는 방법 뿐이었다.
하지만, 블랙리스트에 Access Token까지 저장한다면?
탈취된 Access Token을 곧바로 서버 측에서 블랙 리스트에 저장해 버리면, 해당 Access Token으로 오는 요청을 거절할 수 있게 된다.
매우 좋은 방식인 것 같은데…
이 방식을 사용하게 되면 세션을 사용한 방식과 사실상 차이가 없어진다.
모든 요청이 들어왔을 때, Access Token이 블랙리스트(테이블)에 존재하는지 매 번 데이터베이스를 찔러 조회해 확인해봐야 하기 때문에 JWT 방식을 사용하지만 매우 stateful
한 방식으로 전혀 장점을 살리지 못하고 있다고 볼 수 있다.
이 경우에는 그냥 세션 방식을 사용하는 것과 다를 것이 없으므로, 세션 방식을 사용하는 편이 낫다.
💋 세션
VS JWT & 블랙리스트
이쯤되니 그런 생각이 든다.
그냥 세션 방식을 사용할 걸 그랬나?
하지만, 블랙리스트에 Access Token까지 저장하는 경우가 아닌 Refresh Token만 저장하는 경우에는 아직 이점이 있다.
따라서 여기에서 설명하는 JWT 방식은 로그아웃 시에 블랙리스트에 Refresh Token을 저장하는 방식
이라고 생각하면 된다.
세 가지 상황으로 나누어 생각해 보자.
로그인 성공 시
- 세션 방식: 여전히 세션 DB에 접근해 유저 정보를 저장하고 세션 ID를 반환 받아야 한다.
- JWT 방식: DB에 접근할 필요 없이 토큰만 발급하면 된다.
로그인이 되어 있는 경우
- 세션 방식: 늘 세션 ID가 유효한지 DB를 조회해야 한다.
- JWT 방식: DB에 별도로 조회할 필요 없이, 서명만 확인해서 토큰이 조작되지 않았는지만 확인하면 된다.
로그아웃 시
- 세션 방식: 세션 DB에서 해당 세션 ID에 해당하는 레코드를 삭제해야 한다
- JWT 방식: DB의 블랙리스트 테이블에 로그아웃한 계정의 Refresh Token을 저장하면 된다.
여전히 JWT 방식은 세 가지 중 오직 로그아웃 시에만 데이터베이스에 접근하면 된다.
일부분 stateful
해지긴 했으나, 여전히 stateless
의 장점을 잘 살리고 있다고 볼 수 있다.
💋 번외: 악성 해커의 공격은 어떻게 막을 수 있을까?
일반적인 해결책은 아래와 같다.
✔️ 토큰의 만료 시간을 짧게 설정
토큰의 만료 시간을 짧게 설정하여 토큰의 유효 기간을 최소화한다.
만약 토큰이 탈취되었더라도 짧은 시간 동안만 유효하므로 해커가 토큰을 오랫동안 사용할 수 있는 시간을 제한할 수 있다.
✔️ 서버 측에서 해당 토큰을 무효화
토큰을 탈취한 것을 감지하고, 서버 측에서 해당 토큰을 무효화하는 작업을 할 수 있다.
물론 이미 발급한 토큰에 대한 제어가 필요하므로, 서버에서 토큰을 저장하는 과정 등 추가적인 메커니즘이 필요하다.
예를 들어 데이터베이스에 저장된 무효화된 토큰인지, 매 요청마다 토큰의 유효성을 확인하는 과정을 추가로 구현하여 탈취된 토큰을 거부할 수 있다.
✔️ IP 주소나 기기 정보를 토큰에 포함 (추가 보안 요소)
추가적인 보안 요소를 도입할 수 있다.
예를 들어, IP 주소나 기기 정보를 토큰에 포함시켜 토큰을 사용하는 클라이언트의 신원을 확인할 수 있다.
이를 통해 탈취된 토큰이 다른 기기나 IP 주소에서 사용되는 것을 방지할 수 있다.
이번 포스팅에서는 JWT 방식을 사용해 로그인을 구현했을 때, 로그아웃 및 Refresh Token 발급은 어떻게 해야 할 지에 대해서 알아보았다.
다음 포스팅에서는 직접 코드를 통해 구현해보자!
💋 참고자료
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
'PROJECT > Stamp Crush' 카테고리의 다른 글
[우테코] 인덱스를 활용한 스탬프크러쉬의 쿼리 성능 개선 (1) | 2023.10.07 |
---|---|
[우테코] 스탬프크러쉬 서비스 사용 카페 출장 영업 일기 (25) | 2023.10.05 |
[우테코] 스탬프크러쉬에 실제 사용자(카페 사장)가 생겼어요! (10) | 2023.09.26 |
[우테코] 스탬프크러쉬의 인프라 개선: 현재 아키텍처와 개선 아키텍처 2가지 (0) | 2023.09.14 |
[우테코] Flyway를 사용한 데이터베이스 스키마 형상 관리 (0) | 2023.09.13 |