JWT를 사용하여 로그인 구현하기

김문성·2024년 4월 2일

spring boot jwt

목록 보기
1/2

저번 JWT 포스팅에서 JWT 자체에 대한 이론적인 내용을 살펴봤다면, 이번 포스팅에서는 로그인시 JWT 구현에 앞서 구현할 JWT에 대한 큰 그림을 포스팅하려고 한다.

JWT 구현 과정

1. 인증 로직 구현

인증 로직은 즉, 로그인 과정을 말한다.
클라이언트로 부터 로그인 요청과 함께 아이디/비밀번호가 서버측으로 전달되면, 서버는 회원DB이 저장되어 있는 아이디/비밀번호와 비교하게 된다.
이렇게 인증에 성공한다면, 유저 아이디와 권한을 페이로드에 담은 jwt를 발급한 후 응답 헤더의 "authorization" key의 value로 return해준다.

2. 인가 로직 구현

인가는 로그인이 되어있는 상태에서 진입할 수 있는 페이지에 진입을 하였을 때, 이 사용자가 로그인 후 진입을 했는지 판단하는 것을 말한다.
서버에서 받은 요청 헤더의 "authorization" key가 존재하고 그 value 로 들어있는 jwt가 유효한 jwt인지를 확인함으로써 인가가완료된다.

3. 단일 토큰 구조를 access token, refresh token 구조로 변경

서비스를 만들다 보면 인가가 필요한 서비스가 굉장히 많기 때문에 헤더에 jwt를 담고 보내지는 요청 또한 굉장히 많다. 그만큼 탈취의 위험도 높아지게 된다.
이에 따라 jwt를 access token과 refresh token으로 나누게 된다. 기존의 jwt의 역할을 하는, 인가를 위해 필요한 토큰을 access token으로 한 후 이 access token은 유효기간을 짧게(약 5분) 지정한다. 만약 access token이 만료된 경우 유효 기간이 훨씬 긴 refresh token을 통해 access token을 재발급 해준다. 이 경우 access token은 자주 보내지기 때문에 탈취의 위험이 높지만 탈취당하더라도 유효기간이 짧아 피해가 적어질 것이고, refresh token의 경우 유효기간이 길어 탈취당할 경우 피해가 오래 지속되지만 자주 오가지 않기 때문에 탈취 위험이 줄어들게 된다.
이 때, 탈취당한 후의 피해 유형을 고려하여 access token은 주로 클라이언트의 로컬 스토리지에, refresh token은 주로 쿠키에 저장해준다

4. refresh token rotate 구현

위 구현으로 인해 refresh token의 탈취 위험이 줄어들더라도 탈취당할 가능성은 여전히 존재한다. 따라서 access token이 만료되어 refresh token을 통해 새로 발급받을 때 refresh token 또한 새로 발급받는 것이다. 이렇게 되면 로그인 유지 시간 자체는 길게 유지가 되지만 탈취당했을 시 피해는 최소화할 수 있다.

5. server의 주도권을 높이기 위해 refresh token을 서버에 저장

refresh token rotate를 구현하였더라도 refresh token을 탈취 당했다고 가정하면, refresh token이 새로 발급되었거나 로그아웃을 통해 refresh token이 사라졌더라도 탈취한 refresh token으로 access token 재발급이 가능하게 된다.
이는 서버에 주도권이 없기 때문이다. 따라서 서버에서 이 refresh token을 db에 저장하여 유효한 토큰이어도 db에 존재하는 refresh token에 대해서만 진행되도록 하는 것이다. 이 때 db로 주로 redis를 많이 사용한다고 한다.

6. /logout 구현

기존 POST /logout 요청이 서버에 오면 spring security 자체 메소드가 진행이 되어 쿠키에 있는 refresh token을 지우는 가정이 일어난다. 하지만 우리는 여기서 db에서 값을 제거하는 과정이 필요하므로 /logout 요청을 처리해줄 메소드들에 대해 custom으로 구현을 해주어야 한다.

JWT 실행 flow

로그인 요청이 들어오면 request에서 username과 password를 추출하여 db에 있는 username, password 와 비교한다.
검증 성공 시, refresh token과 access token 생성 후 refresh token은 쿠키에, access token은 헤더에 담아서 응답을 보내준다. 이 때, refresh token을 DB (Redis)에 저장해준다.

로그인이 되어있는 사용자만 진입할 수 있는 메뉴에 진입했다고 하면, 인가 과정이 필요하다. 즉, 이 사용자가 로그인 되어있는 상태인지를 확인하는 것으로 헤더에 access token을 담아서 보내면 서버측에서 해당 access token을 받아서 이를 검증하는 것이다. 하지만 access token은 유효기간이 짧기 때문에 만료됐을 수도 있다. 그럴 경우 실패 응답을 클라이언트에 보낸다.

해당 실패응답을 받으면 클라이언트는 refresh token을 전달하게 된다. refresh token을 서버측에서 받게 되면 유효한 토큰인지 검증을 하고 db(redis)에 있는 토큰값인지를 확인을 한 후 모두 유효하다면 새로운 access token과 refresh token을 생성하여 전달해주게 된다. 이 때 db상에 있는 기존 refresh token에 대한 정보를 지우고 새로 발급된 refresh token을 db에 넣어주게 된다.
이 맥락에서 로그인을 하여 refresh token이 새로 생성될 때 마다 db에서 기존에 값을 지워야하지 않는지에 대해 의문이었지만, 이는 여러 기기 또는 여러 환경에서 로그인을 할 수 있는데 이 때마다 다른 곳에서의 로그인이 로그아웃되는 현상과 같아지기 때문에 로그인할때 기존 refresh token을 지우는 과정을 진행하지 않고, 대신 refresh token의 TTL값을 지정해두어 db에서 각 refresh token이 일정 시간이 지나면 자동으로 지워지도록 한다.

로그아웃 요청이 들어올 경우, access token은 클라이언트 측에서 지우면 되고 서버는 refresh token의 유효성 체크가 끝난 후 db에서 제거한 후 refresh token에 빈 값을 넣은 쿠키를 생성하여 돌려주게 된다.

참고한 링크들
https://substantial-park-a17.notion.site/JWT-7a5cd1cf278a407fae9f35166da5ab03
https://substantial-park-a17.notion.site/JWT-c0bc9713fc284858ac5b7b69a2403893
https://hudi.blog/refresh-token-in-spring-boot-with-redis/

0개의 댓글