[ JAVA ] Refresh Token + JPA 구현

이진우·2025년 4월 14일

의문?

JWT를 활용한 인증 방식은 이제 많은 웹 애플리케이션에서 기본처럼 쓰이고 있습니다.
그동안 엑세스 토큰 기반으로 인증 처리를 해왔지만, 어느 순간 문득 이런 의문이 들었습니다,
"만료된 AccessToken 을 매번 로그인을 통해 재발급을 받아야하나..?"
JWT 를 공부하면서 accessToken의 만료 시간은 길지 않아야 한다고 배웠었고,
위 질문같은 고민을 해결하기위해 RefreshToken 을 사용한다고 들었습니다.


이번 글에서는 JWT + 리프레시 토큰 기반 인증 흐름을 직접 구현해보며, 그 과정에서 마주한 고민들과 해결 방법들을 정리해보려 한다. 단순히 기능을 붙이는 수준이 아닌, 실무적 관점에서 고려해야 할 보안적 관점과 실전 패턴까지 다뤄볼 예정입니다.

- 프로젝트 기획

refreshToken 은 accessToken을 계속 재발급 할 수 있는 같은 존재이기 때문에 보안에 신경을 기울였습니다.

1. ERD

간단한 refreshToken 동작 구현을 위한 공부용 프로젝트이기에 간단하게 ERD를 구현하였습니다.

2. JAVA 코드

- 토큰 발급 세팅

만약 회원가입 이후 로그인 시도하는 상황이라 가정을 하고 제작하였습니다.

controller

@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);
    }

TokenRes

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

access token 같은 경우에는 수명이 짧고, api 요청마다 누구인지를 판단하기 위해
가볍게 움직여야됩니다.

그래서 보통 access token 의 위치는
Authorization: Bearer <access_token> 같은 HTTP 헤더에 둡니다.

Refresh Token

refresh token 같은 경우에는 토큰이 만료되었을 때 재발급을 위한 키로
탈취 당하면 위험할 수 있는 존재입니다.

이를 방지하기위해 서버에서는 RedisDB 에 저장을 하고,
브라우저에서는 쿠키에 담아 저장을 합니다.

  • 재발급을 위해 refresh token을 읽을 때 저장되어있는 토큰과 일치하는지 확인이 가능합니다.

  • 백엔드에 토큰 재발급 요청만 보내면 쿠키에 있는 리프레시 토큰이 같이 가니까 프론트에서 신경을 안써도되고, 서버와의 검증으로 안전하게 재발급이 가능합니다.

  • 쿠키에 HttpOnly, Secure 옵션을 걸면, 브라우저가 자동으로 서버에 보내줄 수 있습니다.
    또한 JS 로도 접근을 못하기에 보안에도 좋습니다.

profile
개발자 응애입니다

0개의 댓글