diff --git a/build.gradle b/build.gradle index bc9aa9cc..1196e9cf 100644 --- a/build.gradle +++ b/build.gradle @@ -44,9 +44,9 @@ dependencies { //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' - // redis + //redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - + implementation 'org.springframework.session:spring-session-data-redis' } jar { diff --git a/src/main/java/com/drinkeg/drinkeg/apipayLoad/code/status/ErrorStatus.java b/src/main/java/com/drinkeg/drinkeg/apipayLoad/code/status/ErrorStatus.java index 487e827d..cea5ee21 100644 --- a/src/main/java/com/drinkeg/drinkeg/apipayLoad/code/status/ErrorStatus.java +++ b/src/main/java/com/drinkeg/drinkeg/apipayLoad/code/status/ErrorStatus.java @@ -34,7 +34,15 @@ public enum ErrorStatus implements BaseCode { // WineNote Error WINE_NOTE_NOT_FOUND(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "와인 노트가 없습니다."), - NOT_INVALID_SCENT(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "해당 이름의 향이 없습니다."); + NOT_INVALID_SCENT(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "해당 이름의 향이 없습니다."), + + // Member Error + REFRESH_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "리프레쉬 토큰이 없습니다."), + REFRESH_TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "리프레쉬 토큰이 만료되었습니다."), + INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "유효하지 않은 리프레쉬 토큰입니다."), + + // Redis Error + REDIS_NOT_FOUND(HttpStatus.BAD_REQUEST, "REDIS4001", "Redis 설정에 오류가 발생했습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/drinkeg/drinkeg/config/RedisConfig.java b/src/main/java/com/drinkeg/drinkeg/config/RedisConfig.java new file mode 100644 index 00000000..7c2c8185 --- /dev/null +++ b/src/main/java/com/drinkeg/drinkeg/config/RedisConfig.java @@ -0,0 +1,37 @@ +package com.drinkeg.drinkeg.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.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private Integer port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + + return redisTemplate; + } +} \ No newline at end of file diff --git a/src/main/java/com/drinkeg/drinkeg/config/SecurityConfig.java b/src/main/java/com/drinkeg/drinkeg/config/SecurityConfig.java index e09e3b88..dfdf26e1 100644 --- a/src/main/java/com/drinkeg/drinkeg/config/SecurityConfig.java +++ b/src/main/java/com/drinkeg/drinkeg/config/SecurityConfig.java @@ -5,7 +5,7 @@ import com.drinkeg.drinkeg.jwt.JWTUtil; import com.drinkeg.drinkeg.jwt.LoginFilter; import com.drinkeg.drinkeg.oauth2.CustomSuccessHandler; -import com.drinkeg.drinkeg.repository.RefreshRepository; +import com.drinkeg.drinkeg.redis.RedisClient; import com.drinkeg.drinkeg.service.loginService.CustomOAuth2UserService; import com.drinkeg.drinkeg.service.loginService.TokenService; import lombok.RequiredArgsConstructor; @@ -34,7 +34,7 @@ public class SecurityConfig { private final CustomSuccessHandler customSuccessHandler; private final JWTUtil jwtUtil; private final TokenService tokenService; - private final RefreshRepository refreshRepository; + private final RedisClient redisClient; // AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입 private final AuthenticationConfiguration authenticationConfiguration; @@ -79,10 +79,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); http - .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, tokenService), UsernamePasswordAuthenticationFilter.class); + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, tokenService, redisClient), + UsernamePasswordAuthenticationFilter.class); http - .addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class); + .addFilterBefore(new CustomLogoutFilter(jwtUtil, redisClient), LogoutFilter.class); //oauth2 http diff --git a/src/main/java/com/drinkeg/drinkeg/controller/JoinController.java b/src/main/java/com/drinkeg/drinkeg/controller/JoinController.java deleted file mode 100644 index a71a1a7b..00000000 --- a/src/main/java/com/drinkeg/drinkeg/controller/JoinController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.drinkeg.drinkeg.controller; - -import com.drinkeg.drinkeg.apipayLoad.ApiResponse; -import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.JoinDTO; -import com.drinkeg.drinkeg.service.loginService.JoinService; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -public class JoinController { - - private final JoinService joinService; - - @PostMapping("/join") - public ApiResponse joinProcess(@RequestBody JoinDTO joinDTO) { - - joinService.joinProcess(joinDTO); - - return ApiResponse.onSuccess("회원가입 성공"); - } -} \ No newline at end of file diff --git a/src/main/java/com/drinkeg/drinkeg/controller/MemberController.java b/src/main/java/com/drinkeg/drinkeg/controller/MemberController.java new file mode 100644 index 00000000..076ef090 --- /dev/null +++ b/src/main/java/com/drinkeg/drinkeg/controller/MemberController.java @@ -0,0 +1,34 @@ +package com.drinkeg.drinkeg.controller; + +import com.drinkeg.drinkeg.apipayLoad.ApiResponse; +import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.JoinDTO; +import com.drinkeg.drinkeg.service.loginService.MemberService; +import com.drinkeg.drinkeg.service.loginService.TokenService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + private final TokenService tokenService; + + @PostMapping("/join") + public ApiResponse joinProcess(@RequestBody JoinDTO joinDTO) { + + memberService.join(joinDTO); + return ApiResponse.onSuccess("회원가입 성공"); + } + + @PostMapping("/reissue") + public ApiResponse reissue(HttpServletRequest request, HttpServletResponse response) { + + tokenService.reissueRefreshToken(request, response); + return ApiResponse.onSuccess("리프레쉬 토큰 재발급 성공"); + } +} diff --git a/src/main/java/com/drinkeg/drinkeg/controller/ReissueController.java b/src/main/java/com/drinkeg/drinkeg/controller/ReissueController.java deleted file mode 100644 index 6c1881f1..00000000 --- a/src/main/java/com/drinkeg/drinkeg/controller/ReissueController.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.drinkeg.drinkeg.controller; - - -import com.drinkeg.drinkeg.jwt.JWTUtil; -import com.drinkeg.drinkeg.repository.RefreshRepository; -import com.drinkeg.drinkeg.service.loginService.TokenService; -import io.jsonwebtoken.ExpiredJwtException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@ResponseBody -@RequiredArgsConstructor -public class ReissueController { - - - private final JWTUtil jwtUtil; - private final RefreshRepository refreshRepository; - private final TokenService tokenService; - - @PostMapping("/reissue") - public ResponseEntity reissue(HttpServletRequest request, HttpServletResponse response) { - - //get refresh token - String refresh = null; - Cookie[] cookies = request.getCookies(); - for (Cookie cookie : cookies) { - - if (cookie.getName().equals("refreshToken")) { - - System.out.println("--------reissue controller-------"); - - refresh = cookie.getValue(); - System.out.println("ReissueToken: " +refresh); - } - } - - if (refresh == null) { - - //response status code - return new ResponseEntity<>("refresh token null", HttpStatus.BAD_REQUEST); - } - - //expired check - try { - jwtUtil.isExpired(refresh); - } catch (ExpiredJwtException e) { - - //response status code - return new ResponseEntity<>("refresh token expired", HttpStatus.BAD_REQUEST); - } - - // 토큰이 refresh인지 확인 (발급시 페이로드에 명시) - String category = jwtUtil.getCategory(refresh); - - if (!category.equals("refresh")) { - - //response status code - return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); - } - - - //DB에 저장되어 있는지 확인 - Boolean isExist = refreshRepository.existsByRefresh(refresh); - if (!isExist) { - - //response body - return new ResponseEntity<>("invalid refresh token", HttpStatus.BAD_REQUEST); - } - - String username = jwtUtil.getUsername(refresh); - String role = jwtUtil.getRole(refresh); - - //make new JWT - String newAccess = jwtUtil.createJwt("access", username, role, 600000L); - String newRefresh = jwtUtil.createJwt("refresh", username, role, 86400000L); - - //Refresh 토큰 저장하고 기존의 Refresh 토큰 삭제 후에 새 refresh 토큰 저장 - refreshRepository.deleteByRefresh(refresh); - tokenService.addRefreshToken(username, newRefresh, 86400000L); - - //response - response.setHeader("access", newAccess); - response.addCookie(tokenService.createCookie("refreshToken", newRefresh)); - - return new ResponseEntity<>(HttpStatus.OK); - } - - -} diff --git a/src/main/java/com/drinkeg/drinkeg/domain/RefreshToken.java b/src/main/java/com/drinkeg/drinkeg/domain/RefreshToken.java deleted file mode 100644 index a6b7c549..00000000 --- a/src/main/java/com/drinkeg/drinkeg/domain/RefreshToken.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.drinkeg.drinkeg.domain; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.Setter; -//import org.springframework.data.annotation.Id; -//import org.springframework.data.redis.core.RedisHash; - - -@Getter -@Setter -@Entity -//@RedisHash(value = "refreshToken", timeToLive =86400000L) -public class RefreshToken { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - - private String refresh; - private String username; - private String expiration; -} diff --git a/src/main/java/com/drinkeg/drinkeg/jwt/CustomLogoutFilter.java b/src/main/java/com/drinkeg/drinkeg/jwt/CustomLogoutFilter.java index ebed9d4c..978d8b07 100644 --- a/src/main/java/com/drinkeg/drinkeg/jwt/CustomLogoutFilter.java +++ b/src/main/java/com/drinkeg/drinkeg/jwt/CustomLogoutFilter.java @@ -1,6 +1,8 @@ package com.drinkeg.drinkeg.jwt; -import com.drinkeg.drinkeg.repository.RefreshRepository; +import com.drinkeg.drinkeg.apipayLoad.code.status.ErrorStatus; +import com.drinkeg.drinkeg.exception.GeneralException; +import com.drinkeg.drinkeg.redis.RedisClient; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -10,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; import java.io.IOException; @@ -18,7 +21,7 @@ public class CustomLogoutFilter extends GenericFilterBean { private final JWTUtil jwtUtil; - private final RefreshRepository refreshRepository; + private final RedisClient redisClient; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -81,18 +84,19 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response, return; } - // 토큰이 DB에 저장되어 있는지 확인 - Boolean isExist = refreshRepository.existsByRefresh(refresh); - if (!isExist) { + String username = jwtUtil.getUsername(refresh); - // response status code - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; + //DB에 저장되어 있는지 확인 + String redisRefresh = redisClient.getValue(username); + if (StringUtils.isEmpty(redisRefresh) || !refresh.equals(redisRefresh)) { + + //response body + throw new GeneralException(ErrorStatus.INVALID_REFRESH_TOKEN); } // 로그아웃 진행 // Refresh 토큰 DB에서 제거 - refreshRepository.deleteByRefresh(refresh); + redisClient.deleteValue(username); // 쿠키에 저장되어 있는 Refresh 토큰 null값 처리 Cookie cookie = new Cookie("refresh", null); diff --git a/src/main/java/com/drinkeg/drinkeg/jwt/LoginFilter.java b/src/main/java/com/drinkeg/drinkeg/jwt/LoginFilter.java index d18d4997..4d5ad694 100644 --- a/src/main/java/com/drinkeg/drinkeg/jwt/LoginFilter.java +++ b/src/main/java/com/drinkeg/drinkeg/jwt/LoginFilter.java @@ -2,6 +2,7 @@ import com.drinkeg.drinkeg.dto.securityDTO.oauth2DTO.LoginResponse; import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.PrincipalDetail; +import com.drinkeg.drinkeg.redis.RedisClient; import com.drinkeg.drinkeg.service.loginService.TokenService; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; @@ -28,6 +29,7 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final JWTUtil jwtUtil; private final TokenService tokenService; + private final RedisClient redisClient; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { @@ -82,9 +84,8 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR response.addCookie(tokenService.createCookie("refreshToken", refreshToken)); response.setStatus(HttpStatus.OK.value()); - // refresh 토큰 저장 - tokenService.addRefreshToken(username, refreshToken, 864000000L); - + // redis에 refresh 토큰 저장 + redisClient.setValue(username, refreshToken, 864000000L); response.sendRedirect("http://localhost:8080/maindy"); diff --git a/src/main/java/com/drinkeg/drinkeg/oauth2/CustomSuccessHandler.java b/src/main/java/com/drinkeg/drinkeg/oauth2/CustomSuccessHandler.java index 18bc7a1a..10da0f20 100644 --- a/src/main/java/com/drinkeg/drinkeg/oauth2/CustomSuccessHandler.java +++ b/src/main/java/com/drinkeg/drinkeg/oauth2/CustomSuccessHandler.java @@ -3,6 +3,7 @@ import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.PrincipalDetail; import com.drinkeg.drinkeg.jwt.JWTUtil; +import com.drinkeg.drinkeg.redis.RedisClient; import com.drinkeg.drinkeg.service.loginService.TokenService; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -24,6 +25,7 @@ public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler private final JWTUtil jwtUtil; private final TokenService tokenService; + private final RedisClient redisClient; // CustomSuccessHandler(JWTUtil jwtUtil) { @@ -59,9 +61,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.addCookie(tokenService.createCookie("refreshToken", refreshToken)); response.setStatus(HttpStatus.OK.value()); - // refresh 토큰 저장 - tokenService.addRefreshToken(username, refreshToken, 864000000L); - + // redis에 refresh 토큰 저장 + redisClient.setValue(username, refreshToken, 864000000L); response.sendRedirect("http://localhost:8080/maindy"); diff --git a/src/main/java/com/drinkeg/drinkeg/redis/RedisClient.java b/src/main/java/com/drinkeg/drinkeg/redis/RedisClient.java new file mode 100644 index 00000000..539c8c15 --- /dev/null +++ b/src/main/java/com/drinkeg/drinkeg/redis/RedisClient.java @@ -0,0 +1,54 @@ +package com.drinkeg.drinkeg.redis; + +import com.drinkeg.drinkeg.apipayLoad.code.status.ErrorStatus; +import com.drinkeg.drinkeg.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +@RequiredArgsConstructor +public class RedisClient { + + private final RedisTemplate redisTemplate; + + public void setValue(String key, String value, Long expiredMs) { + try { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, value, Duration.ofMillis(expiredMs)); + } catch (Exception e) { + throw new GeneralException(ErrorStatus.REDIS_NOT_FOUND); + } + } + + public String getValue(String key) { + try { + ValueOperations values = redisTemplate.opsForValue(); + if (values.get(key) == null) { + return ""; + } + return values.get(key).toString(); + } catch (Exception e) { + throw new GeneralException(ErrorStatus.REDIS_NOT_FOUND); + } + } + + public void deleteValue(String key) { + try { + redisTemplate.delete(key); + } catch (Exception e) { + throw new GeneralException(ErrorStatus.REDIS_NOT_FOUND); + } + } + + public boolean checkExistsValue(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + throw new GeneralException(ErrorStatus.REDIS_NOT_FOUND); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/drinkeg/drinkeg/repository/RefreshRepository.java b/src/main/java/com/drinkeg/drinkeg/repository/RefreshRepository.java deleted file mode 100644 index 9189ea18..00000000 --- a/src/main/java/com/drinkeg/drinkeg/repository/RefreshRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.drinkeg.drinkeg.repository; - -import com.drinkeg.drinkeg.domain.RefreshToken; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - - - -@Repository -public interface RefreshRepository extends JpaRepository { - - - Boolean existsByRefresh(String refresh); - - @Transactional - void deleteByRefresh(String refresh); -} diff --git a/src/main/java/com/drinkeg/drinkeg/service/loginService/JoinService.java b/src/main/java/com/drinkeg/drinkeg/service/loginService/MemberService.java similarity index 92% rename from src/main/java/com/drinkeg/drinkeg/service/loginService/JoinService.java rename to src/main/java/com/drinkeg/drinkeg/service/loginService/MemberService.java index 14e5fb2a..41499f9b 100644 --- a/src/main/java/com/drinkeg/drinkeg/service/loginService/JoinService.java +++ b/src/main/java/com/drinkeg/drinkeg/service/loginService/MemberService.java @@ -9,12 +9,12 @@ @Service @RequiredArgsConstructor -public class JoinService { +public class MemberService { private final MemberRepository memberRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; - public void joinProcess(JoinDTO joinDTO) { + public void join(JoinDTO joinDTO) { String username = joinDTO.getUsername(); String password = joinDTO.getPassword(); diff --git a/src/main/java/com/drinkeg/drinkeg/service/loginService/TokenService.java b/src/main/java/com/drinkeg/drinkeg/service/loginService/TokenService.java index b72ee2ee..7857b864 100644 --- a/src/main/java/com/drinkeg/drinkeg/service/loginService/TokenService.java +++ b/src/main/java/com/drinkeg/drinkeg/service/loginService/TokenService.java @@ -1,20 +1,25 @@ package com.drinkeg.drinkeg.service.loginService; -import com.drinkeg.drinkeg.domain.RefreshToken; -import com.drinkeg.drinkeg.repository.RefreshRepository; +import com.drinkeg.drinkeg.apipayLoad.code.status.ErrorStatus; +import com.drinkeg.drinkeg.exception.GeneralException; +import com.drinkeg.drinkeg.jwt.JWTUtil; +import com.drinkeg.drinkeg.redis.RedisClient; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; - -import java.util.Date; +import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class TokenService { - private final RefreshRepository refreshRepository; - + private final JWTUtil jwtUtil; + private final RedisClient redisClient; public Cookie createCookie(String key, String value) { @@ -27,17 +32,71 @@ public Cookie createCookie(String key, String value) { return cookie; } - public void addRefreshToken(String username, String refresh, Long expiredMs) { + public void reissueRefreshToken(HttpServletRequest request, HttpServletResponse response) { - Date date = new Date(System.currentTimeMillis() + expiredMs); + //get refresh token + String refresh = null; + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { - RefreshToken refreshToken = new RefreshToken(); - refreshToken.setUsername(username); - refreshToken.setRefresh(refresh); - refreshToken.setExpiration(date.toString()); + if (cookie.getName().equals("refreshToken")) { - refreshRepository.save(refreshToken); - } + System.out.println("--------reissue controller-------"); + + refresh = cookie.getValue(); + System.out.println("ReissueToken: " +refresh); + } + } + + if (refresh == null) { + + //response status code + throw new GeneralException(ErrorStatus.REFRESH_TOKEN_NOT_FOUND); + } + + //expired check + try { + jwtUtil.isExpired(refresh); + } catch (ExpiredJwtException e) { + + //response status code + throw new GeneralException(ErrorStatus.REFRESH_TOKEN_EXPIRED); + } + + // 토큰이 refresh인지 확인 (발급시 페이로드에 명시) + String category = jwtUtil.getCategory(refresh); + if (!category.equals("refresh")) { + //response status code + throw new GeneralException(ErrorStatus.INVALID_REFRESH_TOKEN); + } + + String username = jwtUtil.getUsername(refresh); + String role = jwtUtil.getRole(refresh); + + //DB에 저장되어 있는지 확인 + String redisRefresh = redisClient.getValue(username); + if (StringUtils.isEmpty(redisRefresh) || !refresh.equals(redisRefresh)) { + + //response body + throw new GeneralException(ErrorStatus.INVALID_REFRESH_TOKEN); + } + + //make new JWT + String newAccess = jwtUtil.createJwt("access", username, role, 600000L); + String newRefresh = jwtUtil.createJwt("refresh", username, role, 86400000L); + + //Refresh 토큰 저장하고 기존의 Refresh 토큰 삭제 후에 새 refresh 토큰 저장 + redisClient.deleteValue(username); + redisClient.setValue(username, newRefresh, 864000000L); + + // refreshRepository.deleteByRefresh(refresh); + // tokenService.addRefreshToken(username, newRefresh, 86400000L); + + //response + response.addCookie(createCookie("accessToken", newAccess)); + response.addCookie(createCookie("refreshToken", newRefresh)); + response.setStatus(HttpStatus.OK.value()); + } }