JWT를 활용한 인증 방식은 이제 많은 웹 애플리케이션에서 기본처럼 쓰이고 있습니다.
그동안 엑세스 토큰 기반으로 인증 처리를 해왔지만, 어느 순간 문득 이런 의문이 들었습니다,
"만료된 AccessToken 을 매번 로그인을 통해 재발급을 받아야하나..?"
JWT 를 공부하면서 accessToken의 만료 시간은 길지 않아야 한다고 배웠었고,
위 질문같은 고민을 해결하기위해 RefreshToken 을 사용한다고 들었습니다.
이번 글에서는 JWT + 리프레시 토큰 기반 인증 흐름을 직접 구현해보며, 그 과정에서 마주한 고민들과 해결 방법들을 정리해보려 한다. 단순히 기능을 붙이는 수준이 아닌, 실무적 관점에서 고려해야 할 보안적 관점과 실전 패턴까지 다뤄볼 예정입니다.
refreshToken 은 accessToken을 계속 재발급 할 수 있는 키 같은 존재이기 때문에 보안에 신경을 기울였습니다.

간단한 refreshToken 동작 구현을 위한 공부용 프로젝트이기에 간단하게 ERD를 구현하였습니다.
만약 회원가입 이후 로그인 시도하는 상황이라 가정을 하고 제작하였습니다.
@PostMapping("/api/user/login")
public void login(@RequestBody UserReq.Login user, HttpServletResponse response) {
TokenRes login = userService.extractToken(user.toModel());
userService.insertRefreshToken(login.getRefreshToken(), user.getUserId());
cookieUtil.addRefreshTokenToCookie(login.getRefreshToken(), response);
}
private String accessToken;
private String refreshToken;
public static TokenRes of(String accessToken, String refreshToken) {
return new TokenRes(accessToken, refreshToken);
}
로그인 시도 시, 유저의 정보가 넘어오고
jjwt 라이브러리를 사용하여 JwtUtil 에서 회원 pk 를 담는 코드를 구현했습니다.
// ✅ **액세스 토큰 생성 (userKey만 저장)**
public String generateAccessToken(Long userKey) {
return Jwts.builder()
.setSubject(userKey.toString()) // userKey 저장
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getAccessTokenExpireTime()))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
// ✅ **리프레시 토큰 생성 (userKey만 저장)**
public String generateRefreshToken(Long userKey) {
return Jwts.builder()
.setSubject(userKey.toString()) // userKey 저장
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getRefreshTokenExpireTime()))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
accessToken 과 refreshToken 을 각각 생성하고 TokenRes에 담아 controller에 보냈습니다.
이때 갑작스래 궁금증이 생겼습니다.
accessToken 만 사용하는 프로젝트는 토큰을 쿠키에 담아 보내는 로직만하고 재발급은 생각을 못하며 제작했지만,
재발급을 위한 프로젝트이기에 각 토큰의 위치가 궁금해졌습니다.
access token 같은 경우에는 수명이 짧고, api 요청마다 누구인지를 판단하기 위해
가볍게 움직여야됩니다.
그래서 보통 access token 의 위치는
Authorization: Bearer <access_token> 같은 HTTP 헤더에 둡니다.
refresh token 같은 경우에는 토큰이 만료되었을 때 재발급을 위한 키로
탈취 당하면 위험할 수 있는 존재입니다.
이를 방지하기위해 서버에서는 Redis 와 DB 에 저장을 하고,
브라우저에서는 쿠키에 담아 저장을 합니다.
재발급을 위해 refresh token을 읽을 때 저장되어있는 토큰과 일치하는지 확인이 가능합니다.
백엔드에 토큰 재발급 요청만 보내면 쿠키에 있는 리프레시 토큰이 같이 가니까 프론트에서 신경을 안써도되고, 서버와의 검증으로 안전하게 재발급이 가능합니다.
쿠키에 HttpOnly, Secure 옵션을 걸면, 브라우저가 자동으로 서버에 보내줄 수 있습니다.
또한 JS 로도 접근을 못하기에 보안에도 좋습니다.