
JWT는 유효기간이 지나 무효화되기 전까지, JWT 내부 정보를 이용할 수 있습니다.
→ JWT 는 탈취가 쉬움 → 탈취당하게 되면, 보안적 피해가 발생합니다.
이에 대한 해결책은, Access Token 유효기간을 짧게 한다 입니다.
Access Token의 유효기간을 짧게 설정 후,
Refresh Token을 활용하여 Access Token 을 갱신하게 합니다.
그렇게 되면 Access Token 을 탈취당해도 상대적으로 피해를 줄일 수 있습니다.
Refresh Token의 통신 빈도가 적기는 하지만 탈취 위험에서 완전히 벗어난 것은 아니다.
Refresh Token RotationRefresh Token Rotation은 클라이언트가 Access Token를 재요청할 때마다 Refresh Token도 새로 발급받는 것이다.Refresh Token은 더이상 만료 기간이 긴 토큰이 아니게 된다. 따라서 불법적인 사용의 위험은 줄어든다./user/re-issue(서버 Access Token 재발급 API)으로 Http Cookie 에 RefreshToken 을 넣어서 요청한다. (key : refreshToken, value : RefreshToken 값)permitAll() 로 설정하였음@RequiredArgsConstructor
@RequestMapping("/user")
@RestController
public class UserAuthAPI {
private final UserAuthService userAuthService;
@PostMapping("/re-issue")
public ResponseEntity<?> reissue(HttpServletRequest httpServletRequest, @CookieValue(value = "refreshToken") String refreshTokenReq) {
return ResponseEntity.ok(userAuthService.reissue(httpServletRequest, refreshTokenReq));
}
}
@RequiredArgsConstructor
@Service
public class UserAuthService {
private final JwtProvider jwtProvider;
private final RefreshTokenRedisRepository refreshTokenRedisRepository;
...
public TokenInfoResponse reissue(HttpServletRequest httpServletRequest, String refreshTokenReq) {
// 1. 쿠키에서 받아온 refresh token 검증
if (StringUtils.hasText(refreshTokenReq) && jwtProvider.validateToken(refreshTokenReq)) {
// 2. refresh token 타입 검증
jwtProvider.validateTokenType(refreshTokenReq, JwtProvider.TYPE_REFRESH);
// 3. redis에 refreshToken 존재 여부 확인
Optional<RefreshToken> optionalRefreshToken = refreshTokenRedisRepository.findByRefreshToken(refreshTokenReq);
if (optionalRefreshToken.isPresent()) {
RefreshToken refreshToken = optionalRefreshToken.get();
// 4. 최초 로그인한 ip 와 같은지 확인 (처리 방식에 따라 재발급을 하지 않거나 메일 등의 알림을 주는 방법이 있음)
String currentIpAddress = NetworkUtil.getClientIp(httpServletRequest);
if (refreshToken.getIp().equals(currentIpAddress)) {
// 5. Redis 에 저장된 RefreshToken 정보를 기반으로 JWT 생성
TokenInfoResponse response = jwtProvider.generateToken(refreshToken.getId(), refreshToken.getAuthorityList());
// 6. Redis RefreshToken update
refreshTokenRedisRepository.save(RefreshToken.builder()
.id(refreshToken.getId())
.ip(currentIpAddress)
.authorityList(response.authorityList())
.refreshToken(response.refreshToken())
.build());
return response;
}
}
}
throw new CustomCommonException(UserErrorCode.INVALID_REFRESH_TOKEN);
}
}