@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody Map<String, String> loginData) {
// ... 로그인 로직 생략 ...
String accessToken = jwtService.createToken(userId);
String refreshToken = jwtService.createRefreshToken(userId);
userService.updateRefreshToken(userId, refreshToken);
return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken
));
}
JWT 기반 인증 시스템을 구성할 때 가장 중요한 요소는
Access Token과 Refresh Token을 어떻게 설계하고 관리할 것인가이다.
Access Token은 짧은 수명으로 인증을 담당하고, Refresh Token은 인증 상태 유지를 책임진다.
이번 글에서는 이 두 토큰의 구체적인 역할, 만료 처리, 재발급 흐름을 중심으로 정리하려 한다.
Access Token vs Refresh Token
항목 | Access Token | Refresh Token |
목적 | API 요청 인증 | Access Token 갱신 |
수명 | 수분 ~ 수십 분 | 수 시간 ~ 수일 |
저장 위치 | 메모리 / localStorage / Cookie | HttpOnly 쿠키 or DB/Redis |
서버 검증 | JWT 자체 검증 | 서버 저장소에서 추가 검증 필요 |
사용 방식 | Authorization 헤더에 포함 | 서버에 자동 포함 or 직접 전송 |
동작 흐름 요약
1. 로그인 시 토큰 발급
- 사용자가 이메일/비밀번호로 로그인 요청
- 서버에서 인증 성공 시:
- Access Token 생성 → 응답 본문에 포함
- Refresh Token 생성 → DB 저장 및 클라이언트에 쿠키 or 본문 전달
2. Access Token을 통한 API 요청
- 클라이언트는 Access Token을 Authorization: Bearer <token> 형식으로 전송
- 서버는 이 토큰의 서명을 검증하고, 유효하면 요청을 처리
3. Access Token 만료
- Access Token은 보안상의 이유로 만료 주기가 짧음(수 분~수십 분)
- 만료 시 API 요청은 401 Unauthorized를 반환
4. Refresh Token을 통한 Access Token 재발급
- 클라이언트는 저장된 Refresh Token으로 /api/token/refresh API 호출
- 서버는 DB의 Refresh Token과 비교하여 유효성 검증
- 유효하다면 새로운 Access Token을 생성하여 반환
1. Refresh Token 발급 및 저장
// JwtService.java
public String createRefreshToken(Long userId) {
Date now = new Date();
Date expiry = new Date(now.getTime() + Duration.ofDays(7).toMillis());
return JWT.create()
.withClaim("userId", userId)
.withExpiresAt(expiry)
.sign(Algorithm.HMAC256(SECRET));
}
// UserService.java
@Transactional
public void updateRefreshToken(Long userId, String refreshToken) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UsernameNotFoundException("사용자 없음"));
user.setRefreshToken(refreshToken);
userRepository.save(user);
}
- Refresh Token은 **DB(User 테이블)**에 저장하여 토큰 위조나 탈취 시 대응 가능하도록 한다.
- 토큰 유효 기간은 일반적으로 7일 ~ 30일 사이로 설정한다.
2. 로그인 응답 시 토큰 반환
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody Map<String, String> loginData) {
// ... 로그인 로직 생략 ...
String accessToken = jwtService.createToken(userId);
String refreshToken = jwtService.createRefreshToken(userId);
userService.updateRefreshToken(userId, refreshToken);
return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken
));
}
- 클라이언트는 accessToken을 Authorization 헤더에 저장한다.
- refreshToken은 보안을 위해 HttpOnly Cookie 또는 브라우저 외부 저장소에 보관하는 것을 권장한다.
3. Refresh Token을 이용한 Access Token 재발급
@RestController
@RequestMapping("/api/token")
@RequiredArgsConstructor
public class TokenController {
private final JwtService jwtService;
private final UserService userService;
@PostMapping("/refresh")
public ResponseEntity<Map<String, String>> refresh(@RequestBody Map<String, String> request) {
String refreshToken = request.get("refreshToken");
// 1. 토큰 자체 검증
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256("aVeryLongAndSecureSecretKeyForJWTAuthentication123!@#"))
.build().verify(refreshToken);
Long userId = decodedJWT.getClaim("userId").asLong();
// 2. 저장된 Refresh Token과 비교
User user = userService.findByRefreshToken(refreshToken)
.orElseThrow(() -> new RuntimeException("유효하지 않은 Refresh Token"));
// 3. 새 Access Token 발급
String newAccessToken = jwtService.createToken(userId);
return ResponseEntity.ok(Map.of("accessToken", newAccessToken));
}
}
보완 포인트
- Refresh Token 자체가 유효한지 검증한다. (verify)
- DB에 저장된 값과 일치하는지 비교한다.
- 일치하지 않을 경우 → 탈취/위조로 간주하고 로그인 재요청
마무리
- Access Token은 인증의 핵심이지만 수명이 짧고, 이를 보완하는 역할을 Refresh Token이 담당한다.
- Refresh Token을 DB나 Redis에 저장하고, 재발급 요청 시 철저히 검증해야 한다.
- 토큰 저장 위치, 전송 방식, 검증 순서 등 모든 요소가 보안에 직접적으로 연결되므로 구조적으로 신중하게 설계해야 한다.
'프로젝트 > 기업 일정 관리 웹' 카테고리의 다른 글
REST API 에서 Enum 직렬화 문제 (0) | 2025.04.02 |
---|---|
Redis 적용하기 (0) | 2025.04.02 |
도메인 별 SSL 인증서 문제 해결 (0) | 2025.04.02 |
CORS 정책 오류 해결 (0) | 2025.04.02 |
JWT 인증 시스템 구현 (0) | 2025.04.02 |