팀 프로젝트를 진행하면서, JWT 기반 로그인 기능을 구현한 후에, 로그아웃 기능 또한 구현해야 했다.
여러 로그아웃 구현 방법들을 찾아보다가, 세션 로그인의 경우에는 세션에서 해당 유저 데이터를 삭제하면 되지만, 토큰의 경우에는 이미 발급된 토큰을 무효화 시키거나 조작할 수 없다고 한다. 따라서 주로 요청시에 보낸 토큰을 블랙리스트에 넣어 다음 요청부터는 유효하지 않도록 설정하는 식으로 구현하였다.
Redis 다운 링크 : https://github.com/microsoftarchive/redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Redis를 설치한 후에, gradle에 위와 같이 의존성을 추가해준다.
RedisConfig.java
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
// 로그아웃
@PostMapping("/members/logout")
public ResponseEntity<Void> logout(@RequestHeader("Authorization") String accessToken) {
memberService.logout(accessToken);
return new ResponseEntity(HttpStatus.OK);
}
MemberService.java
@Transactional
public void logout(String accessToken) {
Long expiration = jwtTokenProvider.getExpiration(accessToken);
redisTemplate.opsForValue()
.set(accessToken, "blackList", expiration, TimeUnit.MILLISECONDS);
}
JwtAuthenticationFilter.java
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
private final RedisTemplate redisTemplate;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 헤더에서 JWT 를 받아옴
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 토큰인지 확인
if (token != null && jwtTokenProvider.validateToken(token)) {
String isBlackList = (String)redisTemplate.opsForValue().get(token);
// 블랙리스트에 해당 토큰이 존재하지 않을 경우
if (ObjectUtils.isEmpty(isBlackList)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옴
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
if (ObjectUtils.isEmpty(isBlackList))
전체 소스코드는 위 깃허브에서 확인할 수 있다.