3. JWT 로그아웃 프로세스 구현

선종우·2023년 8월 15일
0

1. 로그아웃 프로세스

  • 세션 기반의 인증 프로세스는 로그아웃 시 해당 세션을 만료시키면 된다. 스프링 시큐리티의 경우 기본적으로 등록된 Logoutfilter가 해당 기능을 수행한다.
  • 그러나 이번 프로젝트에서는 JWT토큰을 사용하기로 하였고, JWT 기본 구현은 서버에 정보를 저장하지 않기 때문에 로그아웃을 시킬 방법이 없다.
  • 따라서 결국 토큰을 만료시키기 위해서는 '토큰 블랙리스트'와 같은 방법으로 만료된 토큰 정보를 서버에 저장할 수밖에 없었다.
  • JWT토큰의 특성 상 일정 시간이 지나면 토큰이 만료되기 때문에 DB보다는 특정 시간만 데이터를 저장해놓는 캐시 형태가 더 적절했다.
  • 프로젝트의 규모를 생각했을 때 캐시를 위해 Redis를 사용하는 건 배보다 배꼽이 큰 상황이므로 스프링이 제공하는 캐시 인터페이스와 Caffeine캐시를 이용하기로 결정하였다.

2. 만료 토큰을 관리하기 위한 캐시 설정

  • 토큰의 만료시간 만큼 캐시에서 토큰에 대한 정보를 저장하도록 캐시를 설정하고 캐시이름과 함께 빈으로 등록한다.
CacheConfig.java 중 일부
   @Bean
    public CaffeineCache authCaffeineConfig() {

		
        return new CaffeineCache(CACHE_NAME_MATH_EXPIRED_TOKEN,Caffeine.newBuilder()
                                                        .maximumSize(1000)
                                                        .expireAfterWrite(tokenTime, TimeUnit.MINUTES)
                                                        .build());
    }

3. LogoutFilter 커스터마이징

  • 로그아웃 필터는 크게 LogoutHandler과 Logout SuccessHandler로 구분된다.
  • LogoutHandler는 /logout 경로로 요청이 왔을 때의 행위를 정의하며 기본적으로 Spring Security에 등록되 몇 가지 핸들러 + 내 핸들러의 행위를 추가할 수 있다.
  • 기본 핸들러가 SecurityContext나 세션에 대한 초기화를 처리해주므로 내 핸들러는 사용자의 토큰 정보를 캐시에만 등록해주면 된다.
@RequiredArgsConstructor
@Slf4j
public class CustomLogoutHandler implements LogoutHandler {
    private final CacheManager cacheManager;

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        registerInExpiredToken(authentication);
    }

    private void registerInExpiredToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        if (userPrincipal.token() == null) {
            return;
        }
        String token = userPrincipal.token();
        cacheManager.getCache(CacheConfig.CACHE_NAME_MATH_EXPIRED_TOKEN).put(token, JwtTokenProvider.TOKEN_EXPIRED);
        log.debug("{} is expired", token);
    }
}
  • 로그아웃 과정에서 로그아웃 토큰을 캐시에 등록했으므로 인증과정에서 반드시 캐시를 확인해 요청 토큰이 만료토큰인지를 확인해야 한다.
  • 이후 로그아웃 결과에 대한 적절한 응답을 위해 LogoutHandler를 작성한다.
@Slf4j
@RequiredArgsConstructor
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
    private final ObjectMapper objectMapper;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.debug("[] {} logout success", ((UserPrincipal) authentication.getPrincipal()).id(), ((UserPrincipal) authentication.getPrincipal()).getUsername());



        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        //front 요청으로 모든 응답은 200으로
        response.setStatus(HttpStatus.OK.value());

        Response successResponse = Response.success(ResponseCode.LOGOUT_SUCCESS.getCode());
        String body = objectMapper.writeValueAsString(successResponse);
        response.getWriter().println(body);
    }


}
  • 위 설정을 HttpSecurity 설정에 추가해주면 logout은 구현 완료된다.
  • token 방식의 장점은 서버에 사용자에 관한 정보를 저장하지 않는다는 점인데, 결국 로그아웃을 위해 사용자 정보를 저장할 수밖에 없다. 토큰 방법을 선택한 장점이 퇴색되는 순간이다.

0개의 댓글