JWT 회고 - LikeLionTeam/BootHouse GitHub Wiki
✔과거 생각 했던 JWT
AccessToken, RefreshToken 이 왜 존재하는지에 대해 크게 고민해 보지 않은 거 같습니다. 그냥 단지 보안성을 위해 AccessToken은 짧은 수명, RefreshToken은 긴 수명을 갖는다고 생각했고, 이렇게 함으로써 보안적인 부분은 충분히 커버가 될 거라고 생각했습니다.
// 과거에 토이프로젝트에서 사용했던 JWT 코드 일부
// Refresh 토큰을 따로 저장하지 않고 TokenResponse에 담아 쿠키 형태로 바로 사용
@Builder
public TokenResponse(String accessToken, LocalDateTime accessTokenExpiredAt, String refreshToken, LocalDateTime refreshTokenExpiredAt) {
this.accessToken = accessToken;
this.accessTokenExpiredAt = accessTokenExpiredAt;
this.refreshToken = refreshToken;
this.refreshTokenExpiredAt = refreshTokenExpiredAt;
}
✔현재 생각 하고 있는 JWT
어떤 부분에서 강점이 있어서 ‘세션’ 대신 ‘JWT’ 방식을 선택했는지 이유가 중요하다고 생각합니다. 세션과 토큰의 제가 생각하는 가장 큰 차이점은 state, stateless방식에서의 차이라고 생각합니다. 기본적으로 세션은 서버에 상태를 유지하고 있는 반면, 토큰은 서버에 상태를 유지하고 있지 않습니다. 이점에서 발생되는 파급력은 엄청나다고 생각합니다.
- 기본적으로 ‘온디맨드 클라우드 서버 방식’ 환경에서 어플리케이션을 실행할때 JWT 방식을 사용하면 ‘Scale-Out’을 할때 서버간의 동기화를 하지 않아도 되고, 독립적으로 요청을 처리할 수 있습니다.
- 서버가 인증,인가에 관련된 상태를 갖고 있지 않고 단지 클라이언트측에서 보내는 정보만을 가지고 요청을 처리 하기 때문에 API 간의 통신에서 더 유리하다고 볼 수 있습니다.
하지만 단점도 존재 한다고 생각합니다.stateless 방식이기 때문에 탈취를 당했더라도 서버는 어떤 행동도 할 수 없습니다.
✔개선 방안
기존 방식에서는 사용자가 로그인할 때마다 단순히 TokenResponse를 반환만 하고 RefreshToken을 데이터베이스에 따로 저장 하지 않았습니다. 이번에는 RefreshToken을 데이터베이스에 저장하여 서버가 관리 할 수 있도록 하였습니다.
- 기존
AccessToken은 여전히 짧은 수명을 유지하여 보안성을 확보했습니다.AccessToken이 도난되더라도 짧은 시간 안에 무효화되므로 위험을 최소화할 수 있습니다. AccessToken이 만료될 때RefreshToken을 통해 새로운AccessToken을 발급받을 수 있습니다.- 만약
RefreshToken이 탈취를 당해도 서버는 토큰을 무효화 할 수 있습니다.
public TokenResponse issueTokenResponse(User user) {
refreshTokenRepository.findByUserId(user.getId())
// RefreshToken이 존재한다면
.ifPresentOrElse(refreshToken -> {
// 수명 만료
if(isTimeOutRefreshToken(refreshToken)){
reissueRefreshToken(refreshToken, user); // 재발급
}
}, () -> {
// 없다면 신규발급
issueRefreshToken(user);
});
return createTokenResponse(user);
}
// AccessToken이 만료 되었을때 RefreshToken으로 재발급 여부 판단
public TokenResponse reissueTokenResponse(String refreshTokenValue) {
// 리프레쉬 토큰 값 자체로 데이터베이스에서 검색
RefreshToken refreshToken = refreshTokenRepository.findByToken(refreshTokenValue)
.orElseThrow(() -> new ApiException(TokenErrorCode.INVALID_TOKEN));
if (isTimeOutRefreshToken(refreshToken)) {
throw new ApiException(TokenErrorCode.EXPIRED_TOKEN);
}
// 토큰에서 사용자 ID 추출
Long userId = refreshToken.getUserId();
User user = userRepository.findById(userId)
.orElseThrow(() -> new ApiException(UserErrorCode.USER_NOT_FOUND));
return createTokenResponse(user);
}
✔정리
개선방안을 적용함으로써 JWT의 "stateless”방식 장점을 유지하면서도 사용자의 경험을 향상 개선할 수 있었습니다. AccessToken의 짧은 수명, RefreshToken을 데이터베이스에 저장해서 보안을 강화하는 동시에, RefreshToken을 통해 사용자가 원활하게 서비스에 접근할 수 있도록 했습니다. 이를 통해 사용자에게는 더 나은 경험을 제공하면서도 보안 측면에서의 이점을 유지할 수 있습니다.