[Spring Boot 3.x] accessToken & refreshToken with Redis

2025. 3. 28. 13:32·Spring Boot/Sprng Boot Sample Code

 
 
 
1. JwtProvider

package com.example.springboot3template.common.security;

import com.example.springboot3template.auth.domain.entity.UserRoleEnum;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import java.security.Key;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Slf4j
@Component
public class JwtProvider {
    // Header KEY 값
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // Cookie
    public static final String REFRESH_TOKEN_COOKIE = "RefreshToken";
    // 사용자 권한 값의 KEY
    public static final String AUTHORIZATION_KEY = "auth";
    // Token 식별자
    public static final String BEARER_PREFIX = "Bearer ";
    // 토큰 만료시간
//    private final long ACCESS_TOKEN_TIME = 5 * 60 * 1000L; // 5분
    private final long ACCESS_TOKEN_TIME = 1 * 60 * 1000L; // 5분
    private final long REFRESH_TOKEN_TIME = 14 * 24 * 60 * 60 * 1000L; // 2주


    @Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    @PostConstruct
    public void init() {
        if (secretKey == null || secretKey.isEmpty()) {
            throw new IllegalStateException("JwtProvider에 secretKey가 null입니다.");
        }
        // JwtProvider
        byte[] bytes = Decoders.BASE64.decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);

    }

    // 토큰 생성
    public String createAcessToken(String username, UserRoleEnum role) {
        Date date = new Date();
        String auth = role.getAuthority();
        log.info("AccessToken 생성: username={}, role={}", username, auth);

        return BEARER_PREFIX +
            Jwts.builder()
                .setSubject(username) // 사용자 식별자값(ID)
                .claim(AUTHORIZATION_KEY, auth) // 사용자 권한
                .setIssuedAt(date) // 발급일
                .setExpiration(new Date(date.getTime() + ACCESS_TOKEN_TIME)) // 만료 시간
                .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                .compact();
    }

    public String createRefreshToken(String username) {
        Date now = new Date();
        log.info("RefreshToken 생성: username={}", username);

        return BEARER_PREFIX +
            Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + REFRESH_TOKEN_TIME))
                .signWith(key, signatureAlgorithm)
                .compact();
    }

    // 어세스 토큰 헤더에 추가
    public void addAccessTokenToHeader(String accessToken, HttpServletResponse res) {

        res.setHeader(AUTHORIZATION_HEADER, accessToken);
        log.info("AccessToken이 응답 헤더에 추가되었습니다.");
    }

    // JWT Cookie 에 저장
    public void addRefreshTokenToCookie(String refreshToken, HttpServletResponse res) {

        String token = substringToken(refreshToken);

        Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE, token); // Name-Value
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        cookie.setMaxAge(14 * 24 * 60 * 60); // 2주

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
        log.info("쿠키가 response에 추가되었습니다.");
    }

    // JWT 토큰 substring
    public String substringToken(String tokenValue) {
        if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
            return tokenValue.substring(7);
        }
        log.error("Not Found Token");
        throw new NullPointerException("Not Found Token");
    }

    public SecretKey getSecretKey() {
        return (SecretKey) this.key;
    }

    public long getExpiration(String token) {
        Date expiration = Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .getBody()
            .getExpiration();
        return expiration.getTime() - System.currentTimeMillis();
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .getBody();
        return claims.getSubject();
    }

}

 
 
 
2. JwtFilter
 

package com.example.springboot3template.common.security;

import static com.example.springboot3template.common.security.JwtProvider.AUTHORIZATION_HEADER;
import static com.example.springboot3template.common.security.JwtProvider.REFRESH_TOKEN_COOKIE;
import com.example.springboot3template.auth.application.service.RefreshTokenService;
import com.example.springboot3template.common.globalException.CustomException;
import com.example.springboot3template.common.globalException.ErrorCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.crypto.SecretKey;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;

    private final RefreshTokenService refreshTokenService;

    @Value("${jwt.secret.key}")
    private String secretKey;

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest req, @NonNull HttpServletResponse res, @NonNull FilterChain filterChain)
        throws ServletException, IOException {

        // 요청 url 확인
        String url = req.getRequestURI();

        // 로그인, 회원가입 등의 검증이 필요 없는 url일 경우 필터 제외
        if (isAuthorizationPassRequest(url)) {
            log.info("인증 제외 API 요청: {}", url);
            filterChain.doFilter(req, res);
            return;
        }

        // 헤더에서 어세스 토큰 확인
        String accessToken = getAccessTokenFromHeader(req);
        log.info("요청에서 추출된 토큰: {}", accessToken); // 개발용
//        log.info("토큰 추출 완료");

        SecretKey key = getSecretKey();

        // 1. AccessToken 검증
        if (StringUtils.hasText(accessToken) && validateToken(accessToken, key)) {

//            if (isBlacklisted(accessToken)) {
//                log.warn("AccessToken이 블랙리스트에 포함되어 있음");
//                throw new CustomException(ErrorCode.INVALID_TOKEN);
//            }
            if (refreshTokenService.isBlackListed(accessToken)) {
                log.warn("블랙리스트 토큰입니다.");
                throw new CustomException(ErrorCode.INVALID_TOKEN);
            }

            authenticateUser(accessToken, key);
            log.info("AccessToken 인증 성공");
        }

        filterChain.doFilter(req, res);
    }

    // 토큰 검증
    public boolean validateToken(String token, SecretKey key) {
        if (token == null || token.isEmpty()) {
            log.error("토큰 값이 null 또는 비어있습니다.");
            return false;
        }

        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            log.info("토큰 검증 성공");
            return true;

        } catch (SecurityException | MalformedJwtException | SignatureException e) {
            log.error("유효하지 않은 JWT 서명 또는 포맷 오류: {}", e.getMessage());
            throw new CustomException(ErrorCode.INVALID_TOKEN);
        } catch (ExpiredJwtException e) {
            log.error("ExpiredJwtException - 만료된 토큰: {}", e.getMessage());
            throw new CustomException(ErrorCode.UNAUTHORIZED);
        } catch (UnsupportedJwtException e) {
            log.error("UnsupportedJwtException - 지원하지 않는 토큰: {}", e.getMessage());
            throw new CustomException(ErrorCode.INVALID_TOKEN);
        } catch (IllegalArgumentException e) {
            log.error("IllegalArgumentException - 잘못된 인자: {}", e.getMessage());
            throw new CustomException(ErrorCode.INVALID_TOKEN);
        }
    }

    // 헤더에서 어세스 토큰 가져오기
    public String getAccessTokenFromHeader(HttpServletRequest req) {
        String bearerToken = req.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        log.warn("Authorization 헤더가 없거나 형식이 잘못되었습니다.");
        return null;
    }

    public String getRefreshTokenFromCookie(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if (cookies == null || cookies.length == 0) {
            log.warn("요청에 쿠키가 포함되어 있지 않습니다.");
            throw new CustomException(ErrorCode.UNAUTHORIZED);
        }

        for (Cookie cookie : cookies) {
            log.info("쿠키 이름: {}, 쿠키 값: {}", cookie.getName(), cookie.getValue()); // 개발용
//            log.info("쿠키 이름: {}", cookie.getName());
            if (REFRESH_TOKEN_COOKIE.equals(cookie.getName())) {
                String cookieValue = cookie.getValue();
                if (StringUtils.hasText(cookieValue)) {
                    log.info("디코딩된 토큰: {}", cookieValue); // 개발용
//                    log.info("토큰 디코딩 완료");
                    return cookieValue;
                } else {
                    log.error("RefreshToken 쿠키는 존재하지만 값이 비어있거나 null입니다.");
                    throw new CustomException(ErrorCode.UNAUTHORIZED);
                }
            }
        }
        log.warn("RefreshToken 쿠키가 요청에 포함되지 않았습니다.");
        throw new CustomException(ErrorCode.UNAUTHORIZED);
    }

    // 토큰에서 사용자 정보 가져오기
    public Claims getUserInfoFromToken(String token, SecretKey key) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }

    // 인증 통과 url
    private boolean isAuthorizationPassRequest(String path) {
        return path.startsWith("/api/v1/auth/") || path.startsWith("/api/v1/auth/sign-up");
    }

    // 디코딩 된 시크릿 키 전달
    private SecretKey getSecretKey() {
        if (secretKey == null || secretKey.isEmpty()) {
            throw new IllegalStateException("JwtFilter에 secretKey가 null입니다.");
        }
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
    }

    // 인증 객체 생성
    private void authenticateUser(String token, SecretKey key) {
        Claims claims = getUserInfoFromToken(token, key);
        String username = claims.getSubject();

        // 유저 정보 로드
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        // 인증 객체 생성 및 SecurityContextHolder에 저장
        UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(authentication);

        log.info("인증 완료: {}", authentication); // 개발용
//        log.info("인증 완료");
    }

}

 
 
 
3. RedisConfig

package com.example.springboot3template.common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String redisHost;

    @Value("${spring.data.redis.port}")
    private int redisPort;

    @Value("${spring.data.redis.password}") // 비밀번호
    private String redisPassword;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
        if (!redisPassword.isEmpty()) {
            config.setPassword(RedisPassword.of(redisPassword));
        }
        return new LettuceConnectionFactory(config);
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

 
 
4. RedisTokenService

package com.example.springboot3template.auth.application.service;

import com.example.springboot3template.common.security.JwtProvider;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class RefreshTokenService {

    private final RedisTemplate<String, String> redisTemplate;

    private final JwtProvider jwtProvider;

    // 리프레시 토큰 저장
    public void saveRefreshToken(String username, String refreshToken, long expirationTimeMillis) {
        String token = jwtProvider.substringToken(refreshToken);
        redisTemplate.opsForValue().set("RT:" + username, token, expirationTimeMillis, TimeUnit.MILLISECONDS);
        log.info("리프레시 토큰 저장 완료 - username={}, refreshToken={}", username, token); // 개발용
//        log.info("리프레시 토큰 저장 완료 - username={}, refreshToken 발급완료", username); // 운영용
    }

    // 리프레시 토큰 조회
    public String getRefreshToken(String username) {
        return redisTemplate.opsForValue().get("RT:" + username);
    }

    // 리프레시 토큰 삭제
    public void deleteRefreshToken(String username) {
        redisTemplate.delete("RT:" + username);
        log.info("리프레시 토큰 삭제 완료 - username={}", username);
    }

    // 로그아웃 시 블랙리스트 처리 (옵션)
    public void addBlackList(String accessToken, long expirationTimeMillis) {
        String key = "BL:" + accessToken;
        redisTemplate.opsForValue().set(key, "logout", expirationTimeMillis, TimeUnit.MILLISECONDS);
        log.info("액세스 토큰 블랙리스트 등록 완료 - key={}, expiresIn={}ms", key, expirationTimeMillis);
    }
    // 블랙리스트 여부 확인 (옵션)
    public boolean isBlackListed(String accessToken) {
        String result = redisTemplate.opsForValue().get(accessToken);
        return "logout".equals(result);
    }
}

 
 
5. AuthController
 

package com.example.springboot3template.auth.presentation.controller;

import com.example.springboot3template.auth.application.dto.req.LoginReq;
import com.example.springboot3template.auth.application.dto.req.SignUpReq;
import com.example.springboot3template.auth.application.dto.res.TokenRes;
import com.example.springboot3template.auth.application.service.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/v1/auth")
@RestController
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;

    // 회원가입
    @PostMapping("/sign-up")
    public ResponseEntity<?> signUp(@Valid @RequestBody SignUpReq req) {
        authService.signUp(req);
        return ResponseEntity.ok().build();
    }

    // 로그인
    @PostMapping("/login")
    public ResponseEntity<TokenRes> login(@Valid @RequestBody LoginReq req, HttpServletResponse res) {
        TokenRes tokenRes = authService.login(req, res);
        return ResponseEntity.ok().body(tokenRes);
    }

    // 로그아웃
    @PostMapping("/logout")
    public ResponseEntity<?> logout(HttpServletRequest req) {
        authService.logout(req);
        return ResponseEntity.ok().build();
    }

    // 엑세스 토큰 재발급
    @PostMapping("/reissue")
    public ResponseEntity<TokenRes> reissueAccessToken(HttpServletRequest request, HttpServletResponse response) {
        TokenRes tokenRes = authService.reissueAccessToken(request, response);
        return ResponseEntity.ok().body(tokenRes);
    }

}

 
 
6. TokenRes

package com.example.springboot3template.auth.application.dto.res;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class TokenRes {

    private String accessToken;

}

 
 
 
7. AuthService
 

package com.example.springboot3template.auth.application.service;

import com.example.springboot3template.auth.application.dto.req.LoginReq;
import com.example.springboot3template.auth.application.dto.req.SignUpReq;
import com.example.springboot3template.auth.application.dto.res.TokenRes;
import com.example.springboot3template.auth.domain.entity.User;
import com.example.springboot3template.auth.domain.entity.UserRoleEnum;
import com.example.springboot3template.auth.infrastructure.repository.UserRepository;
import com.example.springboot3template.common.globalException.CustomException;
import com.example.springboot3template.common.globalException.ErrorCode;
import com.example.springboot3template.common.security.JwtFilter;
import com.example.springboot3template.common.security.JwtProvider;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Optional;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {

    private final JwtFilter jwtFilter;

    private final JwtProvider jwtProvider;
    private static final String ADMIN_TOKEN = "adminToken";

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    private final RefreshTokenService refreshTokenService;

    private static final long REFRESH_TOKEN_TTL = 14 * 24 * 60 * 60 * 1000L;

    // 회원가입
    @Transactional
    public void signUp(SignUpReq req) {
        String username = req.getUsername();
        String password = passwordEncoder.encode(req.getPassword());
        UserRoleEnum role  = req.getRole();

        // 회원 중복 확인
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new CustomException(ErrorCode.DUPLICATE_USERNAME);
        }

        if(role == null) {
            role = UserRoleEnum.USER;
        } else if(role == UserRoleEnum.ADMIN) {
            if(req.getAdminToken() == null || !req.getAdminToken().equals(ADMIN_TOKEN)) {
                throw new CustomException(ErrorCode.INVALID_TOKEN);
            }
        }

        // 사용자 등록
        User user = User.builder()
            .username(username)
            .password(password)
            .role(role)
            .build();

        userRepository.save(user);
    }

    // 로그인
    @Transactional
    public TokenRes login(LoginReq req, HttpServletResponse res) {
        String username = req.getUsername();
        String password = req.getPassword();

        // 사용자 확인
        User user = userRepository.findByUsername(username).orElseThrow(
            () -> new CustomException(ErrorCode.LOGIN_FAIL_USERNAME)
        );

        // 비밀번호 확인
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new CustomException(ErrorCode.LOGIN_FAIL_PASSWORD);
        }

        // 사용자 권한 가져오기
        UserRoleEnum role = user.getRole();

        // 엑세스 토큰 생성
        String accessToken = jwtProvider.createAcessToken(username, role);

        // 리프레시 토큰 생성 및 쿠키에 저장 후 Response 객체에 추가
        String refreshToken = jwtProvider.createRefreshToken(user.getUsername());
        jwtProvider.addRefreshTokenToCookie(refreshToken, res);

        refreshTokenService.saveRefreshToken(username, refreshToken, REFRESH_TOKEN_TTL); // 2주

        // Access Token은 응답 Body로 내려줌
        return new TokenRes(accessToken);
    }

    // 로그아웃
    @Transactional
    public void logout(HttpServletRequest request) {
        String accessToken = jwtFilter.getAccessTokenFromHeader(request);
        String refreshToken = jwtFilter.getRefreshTokenFromCookie(request);

        if (!StringUtils.hasText(accessToken)) {
            throw new CustomException(ErrorCode.INVALID_TOKEN);
        }

        // 1. AccessToken -> 블랙리스트로 등록
        long expiration = jwtProvider.getExpiration(accessToken);
        refreshTokenService.addBlackList(accessToken, expiration);

        // 2. RefreshToken 삭제 (key = username 또는 식별자)
        String username = jwtProvider.getUsernameFromToken(refreshToken);
        refreshTokenService.deleteRefreshToken(username);

        log.info("로그아웃 완료 - accessToken 블랙리스트 처리 & refreshToken 삭제");
    }

    @Transactional
    public TokenRes reissueAccessToken(HttpServletRequest request, HttpServletResponse response) {

        // 1. SecretKey 생성(디코딩 된 상태)
        SecretKey key = jwtProvider.getSecretKey();

        // 2. 리프레시 토큰 꺼내기 (쿠키에서)
        String refreshToken = jwtFilter.getRefreshTokenFromCookie(request);
        if (!StringUtils.hasText(refreshToken)) {
            throw new CustomException(ErrorCode.UNAUTHORIZED);
        }

        // 3. 토큰 유효성 검증
        boolean isValid = jwtFilter.validateToken(refreshToken, key);
        if (!isValid) {
            log.info("토큰이 유효하지 않습니다.");
            throw new CustomException(ErrorCode.UNAUTHORIZED);
        }

        // 4. 토큰에서 사용자 정보 추출
        Claims claims = jwtFilter.getUserInfoFromToken(refreshToken, key);
        String username = claims.getSubject();

        // 5. 사용자 조회 (DB에서)
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

        UserRoleEnum role = user.getRole();

        // Redis에 저장된 refreshToken과 비교
        String savedRefreshToken = refreshTokenService.getRefreshToken(username);
        if (!refreshToken.equals(savedRefreshToken)) {
            log.warn("탈취된 토큰일 수 있습니다.");
            throw new CustomException(ErrorCode.INVALID_TOKEN); // 탈취 가능성
        }

        // 6. 새로운 액세스 토큰 생성
        String accessToken = jwtProvider.createAcessToken(username, role);

        // 7. 응답 헤더에 새로운 액세스 토큰 추가 (선택)
        jwtProvider.addAccessTokenToHeader(accessToken, response);

        // 8. 기존의 리프레시 토큰 삭제
        refreshTokenService.deleteRefreshToken(username);

        // 9. 새로운 리프레시 토큰 발급
        String newRefreshToken = jwtProvider.createRefreshToken(username);

        // 10. 쿠키에 새로 발급된 리프레시 토큰 저장
        jwtProvider.addRefreshTokenToCookie(newRefreshToken, response);

        // 11. 레디스에 새로 발급된 리프레시 토큰 저장
        refreshTokenService.saveRefreshToken(username, newRefreshToken, REFRESH_TOKEN_TTL);

        log.info("AccessToken 재발급 완료 - username: {}", username);

        return new TokenRes(accessToken);
    }

}

 
 
 
 

'Spring Boot > Sprng Boot Sample Code' 카테고리의 다른 글

[Spring Boot 3.x] 6. @ControllerAdvice를 활용한 Global Exception Handling 전역 예외 처리  (0) 2025.03.03
[Spring Boot 3.x] 5. @Valid 어노테이션을 통한 DTO validation 설정  (0) 2025.03.03
Spring Boot 에서 네이버 클라우드 Object Storage 에 파일 업로드  (1) 2025.03.03
[SpringBoot 3.x] 4. @PreAuthorize 어노테이션을 이용한 API 인가 설정  (0) 2025.03.03
[Spring Boot 3.x] 3. JWT 토큰 인증(filter) & API 테스트  (0) 2025.03.01
'Spring Boot/Sprng Boot Sample Code' 카테고리의 다른 글
  • [Spring Boot 3.x] 6. @ControllerAdvice를 활용한 Global Exception Handling 전역 예외 처리
  • [Spring Boot 3.x] 5. @Valid 어노테이션을 통한 DTO validation 설정
  • Spring Boot 에서 네이버 클라우드 Object Storage 에 파일 업로드
  • [SpringBoot 3.x] 4. @PreAuthorize 어노테이션을 이용한 API 인가 설정
Yun-seul
Yun-seul
재능이 없으면 열심히라도 하자
  • Yun-seul
    윤슬
    Yun-seul
  • 전체
    오늘
    어제
    • 분류 전체보기 (24)
      • Minecraft 모드 개발 (2)
      • Java (1)
        • Java 기본 (1)
      • Spring Boot (9)
        • Sprng Boot Sample Code (9)
      • Docker (1)
      • Kubernetes (7)
        • Common (3)
        • GKE(Google Kubernetes Engin.. (4)
        • EKS(Elastic Kubernetes Serv.. (0)
      • Redis (0)
      • AWS (0)
      • Git (0)
      • Reflection (1)
      • Troubleshooting (3)
      • Performance Tuning (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    임시디렉토리
    어세스토큰
    쿠버네티스
    onceperrequestfilter
    kubernetes
    전역예외처리
    에러코드관리
    unable to create tempdir. java.io.tmpdir is set to
    docker
    GKE
    커스텀익셉션
    @value #null #어노테이션 #springboot #springioc #컨테이너
    재요청
    jwt토큰 #로그인 #회원가입 #쿠키 #보안설정
    methodargumentnotvalidexception
    globalexception
    rfc6750
    djava.io.tmpdir
    SpringBoot
    필터2번
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Yun-seul
[Spring Boot 3.x] accessToken & refreshToken with Redis
상단으로

티스토리툴바