현재 Access Token의 유효기간을 7일로 설정해두어서 7일이 지났을 때는 Access Token이 삭제 된다. Refresh Token을 이용하여 토큰 만료 시 Access Token 재발급 프로세스를 구현해보자.
먼저 재발급 프로세스 로직을 시각화해보았다. 밑의 그림을 토대로 뼈대를 잡아 구현할 예정이다.
reissue를 구현하기 전에 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에서 호출할 수 있고, 특정 자원에 대한 권한이 있는지 검사하여 접근 허용 여부 결정
reissue는 JwtAuthenticationFilter를 거치면 안된다.
/auth/reissue
는 필터에서 제외해주는 로직이 필요하다.
스프링 공식문서에 따르면 필터링에서 제외시키고 싶은 request에서 true를 반환 하면 된다.
OncePerRequestFilter
안에 있는 shouldNotFilter메소드를 사용하여 Filter에서 제외시켜줄 수 있다.
최종 구현
refreshToken
에서 Claims
정보를 추출한다. jwtService.isTokenValid()
로 리프레시 토큰이 유효한지 검사한다. refreshTokenService.reissue()
를 통해 새 Access Token을 발급받는다. ReissueAccessTokenResponse
로 묶어 반환한다. JwtException
을 던진다.Access Token 재발급할 때 변경된 profileURL Claim이 반영되지 않는다!
추가 설명 : Claims으로 name, role, profileURL을 사용했지만
/reissue
를 통해서 Access Token 생성할 때 기존에 name, role, profileURL로 만들었던 Refresh Token을 이용하여 Access Token을 재발급 하므로 유저 프로필URL이 바뀌면 Refresh Token에 반영을 시켜줘야한다.
세가지의 해결 방법을 생각해봤다.
방법 1) reissue할 때 데이터베이스에 저장된 값을 넣어줄 수 있다
방법 2) 유저가 프로필을 바꾸면 토큰을 재발급할 수 있도록 프론트에 요청하는 방식
방법 3) Claim의 구조를 변경해주는 방법
platformId+”_”+platformType을 복합키로 사용하고 있었는데 고정값인 platformId, platformType를 사용해서 claim을 구성하는 방식
JWT의 구조
claim이 정확하게 뭔지 파악하기 위해서 JWT의 구조를 한번 더 공부해보자.
Header : 토큰의 타입과 해시 암호화 알고리즘으로 구성
Payload : 토큰에 담을 클레임(claim)으로 구성 name/value의 한쌍으로 구성
Signature : 비밀키를 포함하여 암호화된 상태
방법 1, 2는 치명적인 단점이 존재하여 방법 3을 채택했다.platformId_platformType 복합키로 사용하고 있었으므로 유저 정보 수정시 변경이 되지 않고 유일성을 만족하는 platformId과 platformType를 Claim으로 사용하면 겹치지도 않고 유저가 유저 정보를 변경하더라도 Claim이 변경되지 않아 문제를 해결할 수 있다.
이제 클레임으로 PLATFORM_ID_CLAIM, PLATFORM_TYPE_CLAIM, ROLE_CLAIM 종 세개가 들어간다.
platformType, platformId으로 바뀐 것을 볼 수 있다.
https://jwt.io/에서 JWT 토큰의 내용을 확인할 수 있다.
/reissue
구현을 완료하였다.
postman을 통해 정상적으로 작동하는지 확인하였다. 이제는 유저 정보가 변경되더라도 JWT Token을 재발급할 필요가 없어지며 Refresh Token을 통해서 Access Token만 재발급해주면 된다.
/reissue
추가 후 모든 테스트 통과