프로젝트에서 Spring Security + JWT + Redis 방식으로 인증/인가 로직을 구현하였고, 전체 과정을 기록으로 남겨두려 한다.
Token 재발급 과정을 구현하기 전, 먼저 일반적으로 Access Token, Refresh Token을 활용하는 과정에 대해 정리해 보면 다음과 같다.
1. 사용자가 로그인하면 Access Token, Refresh Token을 발급하고, Refresh Token은 DB에 저장한다.
2. 이후 인증이 필요한 api 호출에 대해 Access Token을 Request Header에 실어서 보낸다.
3. 만약 Access Token이 만료될 경우, Refresh Token을 Request Header에 실어 보낸다.
4. 해당 Refresh Token과 DB에 저장된 Refresh Token이 일치할 경우, Access Token을 재발급한다.
5. 재발급 받은 Access Token을 Request Header에 실어서 다시 인증이 필요한 api를 호출한다.
... 반복
여기서 4번 과정을 Access Token 뿐 아니라 Refresh Token까지 같이 재발급하도록 구현했는데, 그 이유는 다음과 같다.
- 만약 공격자가 Refresh Token을 탈취할 경우, Refresh Token이 만료될 때까지 Access Token을 계속 재발급 받을 수 있게 된다.
- 따라서 Access Token을 재발급 받을 때마다 Refresh Token도 같이 재발급 하도록 구현하여 이전에 발급된 Refresh Token은 효력을 잃게 만든다.
- 하지만 이렇게 구현하게 되면 Refresh Token이 탈취되었을 때 공격자가 재발급을 하게 되면 기존 사용자가 재발급을 못하게 되는데, 이를 보완하기 위해 IP 정보를 함께 저장하고 비교하거나, MFA를 추가하는 등 추가적인 보안 절차를 도입해야 할 것이다.
(본 포스트에서는 우선 Refresh Token 재발급까지만 다루었다.)
위 과정에서 필요한 기능은 다음과 같다.
(1) Access Token, Refresh Token 발급
(2) Refresh Token DB 저장 및 조회
(3) Access Token, Refresh Token 유효성 확인 (만료 여부 포함)
여기서 (1), (3)번은 이전 포스트에서 구현하였다.
👉 [Spring Security, JWT, Redis] JwtTokenProvider, SecurityConfig 구현
따라서 (2)번 기능만 구현하면 되는데, DB에 저장하는 방식과 Redis에 저장하는 방식 모두 다뤄보려 한다.
해당 방식은 말 그대로 DB의 사용자 Table에 Refresh Token 컬럼을 추가하고 저장해 두는 방식이다.
내가 참여했던 프로젝트에서는 MariaDB를 활용했으며, Users Table에 REFRESH_TOKEN 컬럼을 추가하고 관리하는 방식으로 구현하였다.
순서만 간단히 다뤄보면 다음과 같다.
1. Token 재발급 API를 구현한다.
2. Frontend 측에서 Access Token 만료 응답을 받게 될 경우, Request Header에 Refresh Token을 담아 Token 재발급 API로 재발급 요청을 보낸다.
3. DB로부터 해당 사용자의 Refresh Token을 조회하여 Request Header의 Refresh Token과 비교한다.
4. 일치할 경우 Token 재발급 후 DB의 Refresh Token을 update하고, Frontend 측에게 Response로 전달한다.
자세한 구현 과정은 다음 포스트에서 확인할 수 있다.
👉 [Spring Security, JWT, Redis] Refresh Token 활용 Token 재발급 (2) - DB 활용 (RDBMS)
본 방식도 기능 동작에는 문제가 없었지만, DB와의 커넥션 없이 Filter에서만 처리하도록 구현해 보고 싶어 Redis를 도입해 보았다.
해당 방식은 Refresh Token을 DB가 아닌 Redis에 저장해 두는 방식이다. DB가 아닌 Redis에 저장하면 다음과 같은 이점이 있었다.
- Redis 자체가 인메모리 DB라 매 요청마다 외부 DB와의 커넥션이 생성되지도 않고 성능 상 매우 빠르다.
- Key-Value로 저장되므로 보다 직관적으로 저장되었다.
(사용자 아이디 - Refresh Token)- Redis를 활용할 수 있도록 Spring Boot에 이미 다 구축되어 있어서 엄청 편했다.
(본 과정에서는 단순 저장/조회/삭제 만 수행되어 RedisTemplate이 아닌 CrudRepository를 활용하였다.)
(더 많은 이점들도 많을 것이다!!!)
해당 이점들 때문에 Redis를 쓴 것도 있지만, 사실 Redis를 도입한 가장 큰 이유는 Token 재발급 과정에 Controller <-> Service <-> Repository <-> DB
의 모든 계층이 관여하지 않고, 오로지 Filter에서 다 처리하도록 만들 수 있을 것 같았기 때문이다.
이에 따라 Redis를 도입하게 되었고, 본 방식도 순서만 간단히 다뤄보면 다음과 같다.
1. Refresh Token을 DB가 아닌 Redis에 저장한다.
2. Filter의 인증 로직에서 Request Header에 Refresh Token이 담겨오면 Token 재발급 요청으로 간주하고 재발급 과정을 수행한다.
3. Request Header의 Refresh Token과 Redis의 Refresh Token을 비교한 후 재발급 과정을 수행하며, 재발급된 Refresh Token을 Redis에 다시 저장한다.
4. 재발급된 Token은 Frontend 측에게 Response로 전달한다.
해당 방식으로 구현하게 되면 Token 재발급 과정이 DB와의 커넥션 없이 Filter에서 이루어지게 된다.
자세한 구현 과정은 다음 포스트에서 확인할 수 있다.
👉 [Spring Security, JWT, Redis] Refresh Token 활용 Token 재발급 (3) - Redis 활용