1년 전 진행했던 마실가실 프로젝트를 🛠️리팩토링하며 정리한 내용입니다.
스마트 루피를 데려온 이유는 스마트하게 로그아웃을 완성했기 때문입니다.
JWT와 로그아웃(1) 게시물을 8월 23일에 작성하였는데, 약 3주의 시간이 흘렀습니다,,
사실 구현은 이전에 했었는데, 멋진 코드를 붙여넣다보니 내 것인듯 내 것 아닌 내 것 같은 코드가 되어버려서 리리팩토링도 하고, SonarQube 등 설치도 하고.. 스프링 공부도 하고.. 그랬습니다..
서론은 여기서 마치고, 로그아웃 코드를 정리해보도록 하겠습니다.
https://hj0216.tistory.com/939
https://hj0216.tistory.com/945
UserController.java
@RestController
@RequestMapping("api/v2/users")
@RequiredArgsConstructor
public class UserController {
@PostMapping("/logout")
@ResponseStatus(HttpStatus.OK)
public void logout(@RequestBody TokenInfo logoutRequestDto){
userService.logout(logoutRequestDto);
}
}
UserService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class UserService {
public void logout(TokenInfo logoutRequestDto) {
if (!jwtTokenProvider.isValidAccessToken(logoutRequestDto.getAccessToken())) {
throw new BusinessException(INVALID_ACCESS_TOKEN);
}
if (redisUtil.get("RT:" + logoutRequestDto.getRefreshToken()) != null) {
redisUtil.delete("RT:" + logoutRequestDto.getRefreshToken());
}
Long expiration = jwtTokenProvider.getExpiration(logoutRequestDto.getAccessToken());
redisUtil.setBlackList("AT:" + logoutRequestDto.getAccessToken(), "logout", expiration);
}
}
1. 로그아웃 요청 전 Access Token의 유효성 검증
2. Refresh Token을 통한 Access Token 재발급을 막기 위해 Redis에 저장된 Refresh Token이 있으면 삭제
3. Redis에 AccessToken을 BlackList 방식으로 저장하여 해당 토큰을 기반으로 한 Login 방지
* BlackList 방식
특정 토큰을 무효화하기 위해 그 토큰을 블랙리스트에 추가하는 것
JwtTokenProvider.java
@Component // Spring이 이 클래스를 자동으로 스캔하고 빈으로 등록
@RequiredArgsConstructor
public class JwtTokenProvider {
// 토큰 정보를 검증하는 메서드
public boolean isValidAccessToken(String accessToken) {
if(redisUtil.hasKeyBlackList("AT:" + accessToken)){
return false;
}
try {
parseClaims(accessToken);
return true;
} catch (MalformedJwtException e) {
throw new BusinessException(MALFORMED_JWT);
} catch (ExpiredJwtException e) {
throw new BusinessException(EXPIRED_JWT);
} catch (UnsupportedJwtException e) {
throw new BusinessException(UNSUPPORTED_JWT);
} catch (IllegalStateException e) {
throw new BusinessException(ILLEGAL_STATE_JWT);
}
}
private Claims parseClaims(String token) {
Key secretKey = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8));
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
public Long getExpiration(String token) {
Key secretKey = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8));
// Token 남은 유효시간
Date expiration = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody()
.getExpiration();
// 현재 시간
Long now = new Date().getTime();
return (expiration.getTime() - now); // TimeUnit.MILLISECONDS
}
}
Access Token의 유효성 검사를 통해
* 로그아웃한 사용자인지 확인
* token을 통해 claims(JWT 를 이용해 전송되는 암호화된 정보) 추출 시, Exception 발생 여부 확인
JwtAuthenticationFilter.java
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// JWT 토큰의 인증 정보를 현재 쓰레드의 SecurityContext에 저장하는 역할 수행
@Override
public void doFilterInternal(HttpServletRequest request
, HttpServletResponse response
, FilterChain filterChain
) throws IOException, ServletException {
String accessToken = SecurityUtils.resolveToken(request);
if (accessToken != null) {
if(jwtTokenProvider.isValidAccessToken(accessToken)){
Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new BusinessException(LOGOUT_MEMBER);
}
}
filterChain.doFilter(request, response);
}
}
Filter에서 AccessToken의 유효성 검사를 수행하도록 하여 Logout한 멤버인지 확인
⭐ 좀 더 생각해봐야 할 부분 ⭐
* 필터에 유효성 검사를 추가하면 무조건 검사가 진행되는데, JwtTokenProvider에 추가적으로 수행한다면 중복 로직이 아닐까?
🙋♀️
본 포스트는 공부 목적으로 작성하였습니다.
보시는 도중 잘못된 부분이나 개선할 부분이 있다면 댓글로 알려주시면 수정하도록 하겠습니다.
📑
참고 자료
https://slimeland.tistory.com/41
https://jake-seo-dev.tistory.com/77
'PlayGround > 마실가실 리팩토링' 카테고리의 다른 글
[1년 후 마실가실] Spring Security 특정 url 제외 (1) | 2024.09.18 |
---|---|
[1년 후 마실가실] Custom Error 처리 (0) | 2024.09.17 |
[1년 후 마실가실] JWT와 로그아웃(2) RefreshToken (1) | 2024.09.14 |
[1년 후 마실가실] 쉬어가는 마실가실 - SonarQube (2) | 2024.09.08 |
[1년 후 마실가실] 패키지 구조 (3) | 2024.09.03 |