login 메서드)// 로그인
public Tokens login(String email, String password) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new ApiException(ErrorCode.UNAUTHORIZED, "이메일 또는 비밀번호가 다릅니다."));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new ApiException(ErrorCode.UNAUTHORIZED, "이메일 또는 비밀번호가 다릅니다.");
}
LocalDateTime now = LocalDateTime.now();
// Single Session 정책: 기존 활성 토큰 모두 폐기
authRepository.revokeAllActiveByUser(user, now);
// 유저 상태 업데이트
user.setStatus(UserStatus.ONLINE);
user.setLastLoginAt(now);
String accessToken = jwtProvider.createAccessToken(user);
String refreshToken = jwtProvider.createRefreshToken(user.getId());
String hashToken = tokenHashProvider.hashRefreshToken(refreshToken);
// DB에 upsert
authRepository.upsertToken(
user.getId(),
hashToken,
now,
now.plusDays(7),
null
);
return new Tokens(accessToken, refreshToken);
}
설명:
ApiException 발생accessToken 반환 → API 호출 인증용refreshToken 저장 → DB + HttpOnly 쿠키로 관리 가능ONLINE, 마지막 로그인 시간)refresh 메서드)// 리프래쉬 토큰으로 액세스 토큰 발급
@Transactional
public Tokens refresh(String refreshToken) {
if (refreshToken == null)
throw new ApiException(ErrorCode.UNAUTHORIZED, "인증 정보가 유효하지 않습니다.");
try {
Claims claims = jwtProvider.parseAndValidate(refreshToken);
if (!jwtProvider.isRefreshToken(claims)) {
throw new ApiException(ErrorCode.UNAUTHORIZED, "리프레시 토큰 타입이 아닙니다.");
}
Long userId = jwtProvider.getUserId(claims);
String oldHash = tokenHashProvider.hashRefreshToken(refreshToken);
// 기존 refresh token 조회
AuthEntity auth = authRepository.findByTokenHashAndRevokedAtIsNull(oldHash)
.orElseThrow(() -> new ApiException(ErrorCode.UNAUTHORIZED, "로그아웃 또는 폐기된 토큰입니다."));
LocalDateTime now = LocalDateTime.now();
if (auth.getExpiresAt() != null && auth.getExpiresAt().isBefore(now)) {
throw new ApiException(ErrorCode.UNAUTHORIZED, "리프레시 토큰이 만료되었습니다.");
}
if (!auth.getUser().getId().equals(userId)) {
throw new ApiException(ErrorCode.UNAUTHORIZED, "토큰 사용자가 불일치합니다.");
}
UserEntity user = auth.getUser();
// 기존 refresh 토큰 폐기
authRepository.revokeAllActiveByUser(user, now);
// 새 refresh 발급
String newRefreshToken = jwtProvider.createRefreshToken(user.getId());
String newHash = tokenHashProvider.hashRefreshToken(newRefreshToken);
// DB upsert
authRepository.upsertToken(
user.getId(),
newHash,
now,
now.plusDays(7),
null
);
String newAccessToken = jwtProvider.createAccessToken(user);
return new Tokens(newAccessToken, newRefreshToken);
} catch (JwtException | IllegalArgumentException e) {
throw new ApiException(ErrorCode.UNAUTHORIZED, "리프레시 토큰이 유효하지 않습니다.");
}
}
설명:
ApiException