토큰 만료 시 Access Token 재발급 구현

yeong-min·2025년 2월 10일
0
post-thumbnail

1. 배경 및 설계

1.1 배경

현재 Access Token의 유효기간을 7일로 설정해두어서 7일이 지났을 때는 Access Token이 삭제 된다. Refresh Token을 이용하여 토큰 만료 시 Access Token 재발급 프로세스를 구현해보자.

1.2 재발급 프로세스 구현 로직 설계

먼저 재발급 프로세스 로직을 시각화해보았다. 밑의 그림을 토대로 뼈대를 잡아 구현할 예정이다.

  1. 사용자가 로그인하면 서버에서 Access Token / Refresh Token을 발급한다.
  2. 인증이 필요한 요청에 Access Token을 포함해 서버로 보낸다.
  3. 서버는 Access Token이 유효하면 요청을 정상 처리한다.
  4. Access Token이 만료되면 Refresh Token으로 새 Access Token 발급을 요청한다.
  5. 서버는 Refresh Token이 유효하면 새로운 Access Token을 발급한다.
  6. 새 Access Token을 사용하여 다시 인증 요청을 진행한다.

2. 구현

2.1 Spring Security 구성요소 및 동작원리

reissue를 구현하기 전에 Spring Security 구성 요소 및 동작 원리에 대해 알아보자

Spring Security 구성요소

- SecurityContextHolder
보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 컨텍스트에 대한 세부정보가 저장된다.

- SecurityContext
Authentication을 보관하는 역할, SecurityContext를 통해 Authenciation객체를 꺼낼 수 있다.

- Authentication
접근하는 주체의 정보와 권한을 담는 인터페이스

- UsernamePasswordAuthentiacationToken
인터페이스 Authentication을 implements한 하위클래스이고 ID가 Principal이고 Password가 Credential의 역할을 한다.

- AuthenticationProvider
실제 인증을 처리하는 부분, 인증 전의 Authentication객체를 받아서 인증 완료된 객체를 반환하는 역할 

- Authentication Manager
인증은 SpringSecurity의 AuthenticationManager에 등록된 AuthenticationProvider에 의해 처리된다. 인증이 성공하면 인증이 성공한 객체를 생성하여 Security Context에 저장한다. 인증이 실패한 경우에는 AuthenticationException을 발생시킨다.

- UserDetails
인증에 성공하여 생성된 UserDetails객체

- UserDetailsService
UserDetailsService인터페이스는 UserDetail객체를 반환하는 단 하나의 메소드를 가지고 있다.

- GrantedAuthority
사용자(principal)이 가지고 있는 권한을 의미 UserDetailsService에서 호출할 수 있고, 특정 자원에 대한 권한이 있는지 검사하여 접근 허용 여부 결정

Spring Security 동작원리

  1. 사용자가 아이디 비밀번호 입력해서 로그인 요청신호가 온다.
  2. AuthenticationFilter에서 UsernamePasswordAuthenticationToken생성
  3. 생성된 토큰을 AuthenticationManager에게 전달합니다.
  4. AuthenticationManager는 등록된 AuthenticationProvider를 조회하여 인증 요구
  5. AuthenticationProvider는 UserDetailsService를 통해 아이디에 대한 사용자 정보를 DB에서 조회
  6. UserDetails 반환
  7. 인증 정보 확인
  8. 입력받은 비밀번호를 암호화하여 DB의 비밀번호화 매칭되면 UsernameAuthenticationToken을 생성하여 AuthenticationManager로 반환
  9. UsernameAuthenticationToken을 AuthenticationFilter로 전송
  10. UsernameAuthenticationToken을 LoginSuccessHandler로 전송하고 SecurityContextHolder에 저장함

2.2 구현 과정

reissue는 JwtAuthenticationFilter를 거치면 안된다.

/auth/reissue는 필터에서 제외해주는 로직이 필요하다.

스프링 공식문서에 따르면 필터링에서 제외시키고 싶은 request에서 true를 반환 하면 된다.

OncePerRequestFilter안에 있는 shouldNotFilter메소드를 사용하여 Filter에서 제외시켜줄 수 있다.


최종 구현

  1. refreshToken에서 Claims 정보를 추출한다.
  2. jwtService.isTokenValid()로 리프레시 토큰이 유효한지 검사한다.
  3. 유효하다면 refreshTokenService.reissue()를 통해 새 Access Token을 발급받는다.
  4. 새 Access Token과 기존 Refresh Token을 ReissueAccessTokenResponse로 묶어 반환한다.
  5. 토큰 검증에 실패하면 예외를 로그로 남기고 JwtException을 던진다.

3. 고민 및 문제 해결

3.1 문제 상황

Access Token 재발급할 때 변경된 profileURL Claim이 반영되지 않는다!

추가 설명 : Claims으로 name, role, profileURL을 사용했지만 /reissue를 통해서 Access Token 생성할 때 기존에 name, role, profileURL로 만들었던 Refresh Token을 이용하여 Access Token을 재발급 하므로 유저 프로필URL이 바뀌면 Refresh Token에 반영을 시켜줘야한다.

세가지의 해결 방법을 생각해봤다.

방법 1) reissue할 때 데이터베이스에 저장된 값을 넣어줄 수 있다

  • 문제점 : reissue()는 refresh토큰은 변경되지 않으므로 유저가 프로필을 변경하면 아예 다시 auth 로그인해야하는 문제가 생긴다(프로필 변경 시 Access Token, Refresh Token 둘다 변경해야된다)

방법 2) 유저가 프로필을 바꾸면 토큰을 재발급할 수 있도록 프론트에 요청하는 방식

  • 문제점 : 유저의 프로필이 변경될 때 마다 토큰 재발급 로직을 수행하는 것은 불필요한 API가 반복적으로 호출될 수 있다.

방법 3) Claim의 구조를 변경해주는 방법
platformId+”_”+platformType을 복합키로 사용하고 있었는데 고정값인 platformId, platformType를 사용해서 claim을 구성하는 방식

  • 문제점 : 현재 name, role, profileURL로 JWT 구조를 설정해두었으므로 platformId, platformType로 변경해줘야하는 것이 많이 있는데 변경해야하는 귀찮음 빼고는 단점이 없는 것 같다.

JWT의 구조
claim이 정확하게 뭔지 파악하기 위해서 JWT의 구조를 한번 더 공부해보자.

Header : 토큰의 타입과 해시 암호화 알고리즘으로 구성
Payload : 토큰에 담을 클레임(claim)으로 구성 name/value의 한쌍으로 구성
Signature : 비밀키를 포함하여 암호화된 상태


3.2 문제 해결

방법 1, 2는 치명적인 단점이 존재하여 방법 3을 채택했다.platformId_platformType 복합키로 사용하고 있었으므로 유저 정보 수정시 변경이 되지 않고 유일성을 만족하는 platformId과 platformType를 Claim으로 사용하면 겹치지도 않고 유저가 유저 정보를 변경하더라도 Claim이 변경되지 않아 문제를 해결할 수 있다.

이제 클레임으로 PLATFORM_ID_CLAIM, PLATFORM_TYPE_CLAIM, ROLE_CLAIM 종 세개가 들어간다.


3.3 Claim 변경 확인

platformType, platformId으로 바뀐 것을 볼 수 있다.

https://jwt.io/에서 JWT 토큰의 내용을 확인할 수 있다.


4. 최종 결과

4. 1 결과

/reissue 구현을 완료하였다.

postman을 통해 정상적으로 작동하는지 확인하였다. 이제는 유저 정보가 변경되더라도 JWT Token을 재발급할 필요가 없어지며 Refresh Token을 통해서 Access Token만 재발급해주면 된다.


4.2 테스트 통과

/reissue 추가 후 모든 테스트 통과

0개의 댓글