전에 코드가 filter에서 transaction이 설정되어 있다는 것이 마음에 들지 않고 코드나 메소드의 역할( reissueRefreshToken 이 토큰 검증과 토큰 재발행의 다중 역할 담당) 등 우아하지 않다고 생각되어 코드 리펙토링을 했다.
// service layer
@Transactional
public TokenResponseDto signIn(String userId, String pw) {
// uesrId 확인
UserDetails userDetails = myUserDetailsService.loadUserByUsername(userId);
// pw 확인
if(!passwordEncoder.matches(pw, userDetails.getPassword())){
throw new BadCredentialsException(userDetails.getUsername() + "Invalid password");
}
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
// refresh token 발급 및 저장
String refreshToken = jwtTokenProvider.createRefreshToken(authentication);
RefreshToken token = RefreshToken.createToken(userId, refreshToken);
// 기존 토큰이 있으면 수전, 없으면 생성
refreshTokenRepository.findByUserId(userId)
.ifPresentOrElse(
(tokenEntity)->tokenEntity.changeToken(refreshToken),
()->refreshTokenRepository.save(RefreshToken.createToken(userId, refreshToken))
);
// accessToken과 refreshToken 리턴
return TokenResponseDto.builder()
.accessToken("Bearer-"+jwtTokenProvider.createAccessToken(authentication))
.refreshToken("Bearer-"+refreshToken)
.build();
}
기존에 filter 에서 담당하던 jwt refresh token을 이제는 서비스 레이어에서 로그인 할 때 생성 밑 저장해준다. 마찮가지로 refresh token을 검사하고 access token 밑 refresh token을 재발급 해주는 기능도 서비스 레이어로 옮겼다.
@Transactional
public TokenResponseDto reissueAccessToken(String token) {
//token 앞에 "Bearer-" 제거
String resolveToken = resolveToken(token);
//토큰 검증 메서드
//실패시 jwtTokenProvider.validateToken(resolveToken) 에서 exception을 리턴함
jwtTokenProvider.validateToken(resolveToken);
Authentication authentication = jwtTokenProvider.getAuthentication(resolveToken);
// 디비에 있는게 맞는지 확인
RefreshRedisToken refreshRedisToken = refreshRedisRepository.findById(authentication.getName()).get();
// 토큰이 같은지 확인
if(!resolveToken.equals(refreshRedisToken.getToken())){
throw new RuntimeException("not equals refresh token");
}
// 재발행해서 저장
String newToken = jwtTokenProvider.createRefreshToken(authentication);
RefreshRedisToken newRedisToken = RefreshRedisToken.createToken(authentication.getName(), newToken);
refreshRedisRepository.save(newRedisToken);
// accessToken과 refreshToken 모두 재발행
return TokenResponseDto.builder()
.accessToken("Bearer-"+jwtTokenProvider.createAccessToken(authentication))
.refreshToken("Bearer-"+newToken)
.build();
}
발급 받은 access token을 헤더에 Authentication에 넣으면 무사히 "/auth/**" 에 접근 가능합니다.
만약 access token이 만료되었을 경우에 "/api/v1/accessToken"경로로 refresh token을 통해 accesstoken을 재발급 받을 수 있습니다.
h2 db를 사용해서 상당히 빠른모습...
다음에는 redis를 사용해보겠습니다.
참고자료 :
https://kukekyakya.tistory.com/entry/Spring-boot-access-token-refresh-token-발급받기jwt