개발자
[JWT+Redis]Redis를 이용해 최신 JWT만 이용하게 하기 + accessToken 만료시 재발급 + JWT 로그아웃 - 2 본문
[JWT+Redis]Redis를 이용해 최신 JWT만 이용하게 하기 + accessToken 만료시 재발급 + JWT 로그아웃 - 2
GoGo개발 2023. 8. 1. 15:49이전 포스팅에 이어서 이번에는 redis를 이용해서 로그아웃을 구현해 보겠습니다.
balckList를 사용한다고 하는데요 말은 blackList지만 실제로 사용할 때는 redis에 해당토큰을 set해서 blackList로 등록해주고 리소스 접근시 balckList에 해당 토큰이있다면 로그아웃 처리되었기 때문에 리소스 접근이 안되는 것입니다.
flow를 살펴봅시다.
로그아웃 flow는 이렇게 진행됩니다. 핵심은 accessToken을 BalckList에 등록해주고 refreshToken을 삭제해서 해당 토큰으로 로그인할 수 없도록 로그아웃처리를 해줍니다. 그래서 해당 리소스에 요청할때 로그아웃처리된 토큰이 온다면
redis에서 blackList를 확인하여 해당 토큰이 있다면 로그아웃된 토큰이므로 리소스에 접근이 불가합니다.
다시 로그인을 해야하는 것입니다.
이제 이 flow를 구현해볼까요?
이전 포스팅의 내용에서 이어집니다.
logout API 요청이 들어오면 accessToken 검증을 하는 filter는 똑같이 실행됩니다 그 이후 contorller 입니다.
@DeleteMapping("/logout")
public String logout(HttpServletRequest request, Principal principal){
employeeLoginService.logout(request, principal.getName());
return "success";
}
refreshToken을 삭제할때 key값이 id로 되어있기 때문에 로그인되어있는 회원의 ID(토큰주인의 ID)값을 가져와서 service에 인자값으로 넘겨 주어야 합니다.
principal.getName() 이부분인데요. 이미 jwt필터에서 토큰을 복호화해서 토큰 안의 authentication을 spring context에 저장해놨기 때문에 또 반복할 필요 없이 저장되어있는 사용자 정보를 가져오면 되는 것입니다.
저는 사용자 id만 필요하기 때문에 principal객체를 사용했지만 더많은 사용자 정보를 사용하려면 @AuthenticationPrincipa를 사용하면 됩니다.
사용자 정보를 가져올때 처음 @AuthenticationPrincipal 어노테이션을 사용했었는데 null 이 들어오는 오류가 발생해 prinvipal 객체를 가져오는 것으로 바꾸었습니다.
null이 들어오는 이유는 저는 인증 객체를 저장할때 토큰을 발급해주는 API를 제외하고는 토큰에서 추출한 정보만으로 인증 객체를 만들어서 DB에 접근하는 loadUserByUserName 메소드가 실행되지 않기 때문입니다. @AuthenticationPrincipal 는 loadUserByUsername() 메서드의 반환값과 같아야 하기 때문입니다.
나머지는 아래 블로그를 참고해 주세요
service에 왔을때 blackList에 저장하는 메소드를 만들어 줍니다
public void logout(HttpServletRequest request, String username) {
//SecurityContextHolder.clearContext();
//accessToken 추출
String accessToken = jwtTokenProvider.resolveToken(request);
//엑세스 토큰 남은 유효기간
Long expiration = jwtTokenProvider.getExpiration(accessToken);
//Redis BlackList 저장
redisTemplate.opsForValue().set(
accessToken,
"logout",
expiration,
TimeUnit.MILLISECONDS
);
//리프레쉬 토큰 삭제
redisTemplate.delete("RTK"+username);
}
하나씩 살펴봅시다. 첫번째 메소드 토큰을 추출하는 resolveToken은 이전 포스팅과 동일합니다.
이제 accessToken을 blackList에 등록해주어야하는데 남은 유효기간이 필요합니다.
해당 메소드는 아래와 같습니다.
public Long getExpiration(String accessToken){
Date expiration = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(accessToken).getBody().getExpiration();
long now = new Date().getTime();
return expiration.getTime() - now;
}
토큰의 남은 유효기간을 가져오는 메소드 입니다.
그런뒤 accessToken을 Key로 하고 "logout"을 value로해서 redis에 저장해줍니다.(blackList 등록)
그리고 현재 redis에있는 refreshToken을 삭제해 줍니다.
이렇게 하면 로그아웃 처리가 됐습니다. 간단하죠?
이제 로그아웃 처리된 토큰으로 리소스에 접근하려고 할때 처리를 해주어야합니다.
JwtAuthenticationFilter에서 처리를 해줄 건데요. 검증 위치는
1. token resolve
2. blakcList check
3. accessToken validate
이 순서대로 해줄 겁니다. 토큰을 가져와서 accessToken을 검증해주기 전에 넣어주면 되겠죠?
<JwtAuthenticationFilter>
//로그아웃 된 토큰인지 확인
if(StringUtils.hasText(accessToken)) jwtTokenProvider.validateBlackListToken(accessToken);
<JwtTokenProvider>
public void validateBlackListToken(String accessToken){
String blackList = redisTemplate.opsForValue().get(accessToken);
if(StringUtils.hasText(blackList)){
throw new BlackListToken("로그아웃된 사용자 입니다");
}
}
exception은 custom exception을 만들어 줬습니다.
자 이제 redis에 해당 accessToken이 존재한다면 exception이 터지면서 리소스에 접근이 안될것입니다.
굉장히 간단하죠?
이제 테스트를 통해서 확인 해 보도록 하겠습니다.
현재 로그인 처리된 경우 Redis 입니다.
현재 1,2번 두명의 회원이 로그인을 하고 있습니다.
두 회원의 accessToken,RefreshToken 만 존재하는 것을 알 수 있습니다.
이제 1번 회원의 토큰으로 logout API를 요청해보겠습니다. 해당 API는 @DeleteMapping으로 매핑해놓았습니다.
포스트맨으로 요청을 해보았더니
요청은 success로 로그아웃 처리가 되었습니다.
그럼 과연 redis에서도 blackList가 제대로 등록되어있고 refreshToken이 삭제되었는지 확인해 볼까요?
1번 회원의 accessToken이 blackList로 등록되어있고, 1번회원의 refrshToken이 삭제 되었습니다.
accessToken 의 value값인 "logout"도 잘 들어가 있습니다.
그렇다면 1번 회원의 accessToken은 남아있게 되는데 accessToken의 유효기간은 30분으로 짧기 때문에 30분 후 자동으로 redis에서 삭제가 됩니다. blackList도 마찬 가지입니다.
1번 회원의 accessToken ttl 입니다. 1175초 남아있는 것을 알 수 있습니다.
이제 로그아웃된 토큰으로 다른 리소스에 접근해 보겠습니다.
콘솔을 확인해 보면 로그아웃된 사용자라고 뜨며 exception 처리되었습니다.
이렇게 redis를 사용하여 jwt 로그아웃을 구현해 보았습니다.
참고블로그
https://geunzrial.tistory.com/179
//redis 캐시사용
https://developer-nyong.tistory.com/21
[Spring Boot, Database] Spring Boot + Redis 제대로 활용하기(2)
저번 포스팅에서는 레디스라는 데이터베이스 자체에 대해서 자세히 알아보았다. 이번 포스팅에서는 스프링 부트에서 레디스를 사용하는 법을 알아보자. Spring Boot에서 레디스 설정하기 스프링
developer-nyong.tistory.com
'개발자 > workflow 리팩토링 프로젝트(SpringBoot,JPA,MySQL)' 카테고리의 다른 글
[Srping Security + JWT] JWT 로그인/토큰 재발급 동작 flow (0) | 2023.09.12 |
---|---|
[SpringBoot]멀티모듈에서 개발 환경별로 DB 분리하기 (0) | 2023.09.08 |
[JWT+Redis]Redis를 이용해 최신 JWT만 이용하게 하기 + accessToken 만료시 재발급 + JWT 로그아웃 - 1 (1) | 2023.07.29 |
[JWT]JWT를 왜 쓰는걸까?/토큰과 세션방식의 차이점 (0) | 2023.07.13 |
[JWT]Spring Security + JWT 로그인 구현하기 (0) | 2023.07.02 |