Refresh Token 적용 (1)

김재현·2024년 1월 7일
1

TIL

목록 보기
71/88
post-thumbnail

1. 발생한 문제 : JWT 유효시간

현재 프로젝트에서 클라이언트와 서버간에는 JWT를 이용하여 인증한다.

JWT의 만료시간을 짧게(30분) 설정했기 때문에 30분마다 재로그인해주어야했다.
개발자 팀원은 이러한 불편을 토로하며 내게 JWT의 유효시간의 향상을 요청했다.

2. 해결 방안

단순히 JWT의 만료시간을 대폭 늘려버린다면 즉각적으로 해결 가능하다.
하지만 이것은 만약 토큰이 탈취되었을 경우 보안상의 문제가 발생 할 수 있다.

따라서 Refresh Token을 적용하여 보안을 강화 하면서도 사용자의 편의성을 증가시키기로 결정하였다.

3. 설계

Token을 저장할 DB는 Redis를 선정했다.

Redis DB 사용 이유

  1. 기본적으로 만료기간을 설정 가능하다.
  2. 메모리 기반의 빠른 조회가 가능하다.
    : JWT값 조회만으로는 DB에 큰 부담이 되지 않을 것이며, 혹시 삭제되더라도 위험 부담이 적기 때문에 메모리 기반 DB를 사용 할 수 있다고 판단했다.

4. 구현

UserService

로그인 시 AccessToken은 클라이언트에게 보내고, RefreshToken은 DB에 저장한다.

  public UserResponseDto login(LoginRequestDto requestDto, HttpServletResponse response) {
    
    					... // (아이디 및 패스워드 확인)

    // access token 및 refresh token
    String accessToken = jwtUtil.createAccessToken(loginId);
    response.setHeader(JwtUtil.AUTHORIZATION_HEADER, accessToken);

    String refreshToken = jwtUtil.createRefreshToken(loginId);
    jwtUtil.saveRefreshToken(accessToken.substring(7), refreshToken.substring(7));

    return new UserResponseDto(user);
  }

AuthorizationFilter

Filter에서 Token 만료 여부를 확인 하여 필요에 따라 재발급한다.

AccessToken만 만료되었다면 RefreshToken을 이용해서 다시 AccessToken을 발행하여 사용자에게 전달한다.

사용자는 AccessToken이 만료되었더라도 다시 로그인해야하는 번거로움이 발생하지 않게 된다.

public class JwtAuthorizationFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(...) {
  
    String accessTokenValue = jwtUtil.resolveToken(request);

    if (Objects.nonNull(accessTokenValue)) {

      // accessToken이 만료되었는지 확인
      if (jwtUtil.shouldAccessTokenBeRefreshed(accessTokenValue)) {
        String refreshTokenValue = jwtUtil.getRefreshtokenValue(accessTokenValue);
        // refreshtoken이 유효한지 확인
        if (jwtUtil.validateToken(refreshTokenValue)) {
          // accessToken 재발급
          String accessToken = jwtUtil.createAccessTokenByRefreshToken(refreshTokenValue);
          response.setHeader(JwtUtil.AUTHORIZATION_HEADER, accessTokenValue);
          accessTokenValue = accessToken.substring(7);
        }
        // refreshToken이 유효하지 않다면 재발급 없이 만료된 상태로 진행
      }

					... // Spring Security 인증 객체 생성

    filterChain.doFilter(request, response);
  }


}

보안 관련된 문제는 번거롭기만 한 줄 알았는데, 오히려 사용자가 편의성과 함께 보안이 향상시키는 새로운 경험이었다.

보안과 관련된 것들이 괜시리 멋있고 재미있다. 코드의 허점으로 정보는 빼오는 해커와 그것을 막는 개발자! 재미있지 않은가?
개인적으로 문제점을 찾아내고 그것을 반론(?)하는 그런 과정을 좋아하는데, 어떻게보면 해커와 개발자의 관계는 그렇게 볼 수도 있을 것 같다.

앞으로 보안 관련한 것을 배우고 사용해보고 싶은 소망이 있다.


관련 포스팅

Following Post

profile
I live in Seoul, Korea, Handsome

0개의 댓글