Spring Boot · 2025-12-30

JWT로 구현하는 Spring Boot 인증과 리프레시

Spring Boot에서 JWT 기반 인증과 리프레시 토큰의 개념, 발행·검증·갱신 흐름, 저장 전략과 예외 처리까지 초보자가 이해하기 쉬운 구현방법

작성일 : 2025-12-30 ㆍ 작성자 : 관리자
post
목차

소개

JWT(Json Web Token)는 무상태(stateless) 인증을 쉽게 구현하게 해준다. Spring Boot 환경에서 JWT 토큰으로 인증을 처리하면 서버 확장성이 좋아진다. 다만 액세스 토큰만으로는 보안과 사용자 경험을 모두 만족시키기 어렵다. 그래서 리프레시 토큰을 함께 도입해 토큰 갱신을 안전하게 처리하는 구조가 필요하다.

핵심 개념

액세스 토큰과 리프레시 토큰

액세스 토큰은 짧은 유효기간을 가진 토큰이다. 주로 API 접근 권한을 확인한다. 리프레시 토큰은 상대적으로 긴 유효기간을 가진 토큰이다. 액세스 토큰이 만료되면 클라이언트는 리프레시 토큰으로 새로운 액세스 토큰을 발급받는다.

보안 고려사항

  • 리프레시 토큰은 안전한 저장소에 보관한다. (HTTP-only 쿠키, 서버 DB 등)
  • 리프레시 토큰 탈취 시 피해를 줄이기 위해 토큰 무효화 전략을 마련한다.
  • 액세스 토큰은 짧게 설정하여 노출 위험을 낮춘다.

구현 개요

구현은 다음 흐름으로 진행한다.

  • 사용자 로그인 시 액세스 토큰과 리프레시 토큰 발급
  • 클라이언트는 액세스 토큰으로 API 호출
  • 액세스 토큰 만료 시 리프레시 토큰으로 재발급 요청
  • 서버는 리프레시 토큰 유효성 검증 후 새 액세스 토큰 발급

예제 코드

아래는 핵심 클래스 예시다. 간결하게 핵심만 담았다.

JWT 유틸

package com.example.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtUtil {
  private final String secret = "your-256-bit-secret";
  private final long accessValidity = 1000L * 60 * 15; // 15분
  private final long refreshValidity = 1000L * 60 * 60 * 24 * 7; // 7일

  public String generateAccessToken(String subject) {
    return Jwts.builder()
        .setSubject(subject)
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + accessValidity))
        .signWith(SignatureAlgorithm.HS256, secret)
        .compact();
  }

  public String generateRefreshToken(String subject) {
    return Jwts.builder()
        .setSubject(subject)
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + refreshValidity))
        .signWith(SignatureAlgorithm.HS256, secret)
        .compact();
  }

  public Claims parseToken(String token) {
    return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
  }
}

인증 컨트롤러 (로그인/리프레시)

package com.example.controller;

import com.example.security.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
public class AuthController {
  private final JwtUtil jwtUtil = new JwtUtil();

  @PostMapping("/login")
  public ResponseEntity<Map<String, String>> login(@RequestBody Map<String, String> req) {
    String username = req.get("username");
    // 실제로는 사용자 검증 로직 필요
    String access = jwtUtil.generateAccessToken(username);
    String refresh = jwtUtil.generateRefreshToken(username);
    return ResponseEntity.ok(Map.of("accessToken", access, "refreshToken", refresh));
  }

  @PostMapping("/token/refresh")
  public ResponseEntity<Map<String, String>> refresh(@RequestBody Map<String, String> req) {
    String refresh = req.get("refreshToken");
    try {
      String subject = jwtUtil.parseToken(refresh).getSubject();
      String newAccess = jwtUtil.generateAccessToken(subject);
      return ResponseEntity.ok(Map.of("accessToken", newAccess));
    } catch (Exception e) {
      return ResponseEntity.status(401).build();
    }
  }
}

토큰 저장 전략

리프레시 토큰 저장 위치는 보안에 큰 영향을 준다. 선택지는 주로 다음과 같다.

  • HTTP-only 쿠키: 브라우저 XSS 위험을 줄인다.
  • 서버 DB에 저장: 탈취 시 리프레시 토큰 무효화가 가능하다.
  • 인메모리 캐시(Redis): 분산 환경에서 성능과 무효화 관리에 유리하다.

일반적으로 리프레시 토큰은 서버에서 관리하거나 HTTP-only 쿠키로 전송한다. 클라이언트에만 맡기면 탈취 시 회복이 어렵다.

예외 처리와 무효화

  • 리프레시 토큰 사용 시 토큰 재발급 로그를 남긴다.
  • 로그아웃 또는 비정상 행위 감지 시 DB/캐시에서 리프레시 토큰을 삭제한다.
  • 토큰 재발급 시 토큰 회전(rotation) 전략을 사용하면 보안성이 높아진다.

마무리

Spring Boot에서 spring boot jwt 인증을 구현할 때 액세스 토큰과 리프레시 토큰의 역할을 명확히 구분하면 보안과 사용자 경험을 모두 개선할 수 있다. spring boot refresh token 구현 시 저장 전략과 무효화 절차를 설계하는 것이 핵심이다. 위 예제는 기본 흐름을 보여준다. 실제 서비스에서는 인증 로직, 예외 처리, 로그와 모니터링을 추가해 운영 안전성을 확보해야 한다.

spring boot jwt 인증 spring boot refresh token 구현 jwt refresh token spring boot jwt 인증 refresh token spring security 토큰 기반 인증 액세스 토큰 리프레시 토큰