현재 Jwt를 이용한 로그인 방식을 사용하고 있기에, Jwt 토큰을 재발급 하는 로직이 필요
import axios from "axios";
import Cookies from "js-cookie";
const api = axios.create();
api.interceptors.request.use(
(config) => {
const accessToken = Cookies.get("authorization");
if (accessToken) {
config.headers.authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (
error.response &&
error.response.status === 401 &&
!originalRequest._retry
) {
console.log("Access token이 만료되었습니다. 재발급 시도 중...");
originalRequest._retry = true;
try {
const refreshAuthorization = Cookies.get("refresh_authorization");
console.log("재발급 요청 중 ..");
console.log("refreshAuthorization 값:", refreshAuthorization);
const response = await axios.post(
`/api/reToken`,
{},
{
headers: {
refreshAuthorization: `Bearer ${refreshAuthorization}`,
},
}
);
console.log("재발급 요청 완료");
// 새 Access Token 저장
// 헤더의 토큰을 가져올 때는 response.headers[''] 형식을 지키고 반드시 소문자로 시작해야 함
const newAccessToken = response.headers["authorization"];
if (!newAccessToken) {
console.error("새로운 Access Token을 가져오지 못했습니다.");
return Promise.reject(error);
}
Cookies.set("authorization", newAccessToken, {
secure: true,
sameSite: "Strict",
});
api.defaults.headers.common["authorization"] = `Bearer ${newAccessToken}`;
console.log("Access token이 성공적으로 재발급되었습니다.");
// 기존 요청에 새 토큰을 업데이트
originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
// 새로운 axios 인스턴스를 사용하여 요청을 다시 보냄
return axios({...originalRequest, withCredentials: true });
} catch (err) {
console.error(
"Refresh token이 만료되었거나 오류가 발생했습니다. 로그아웃 처리 필요."
);
return Promise.reject(err);
}
}
return Promise.reject(error);
}
);
export default api;
api로 요청을 보내는 경우 interceptor가 우선 요청을 보낸 후 만약 Jwt 토큰이 만료되게 됐다면
, 발생한 HttpServletResponse.*SC_UNAUTHORIZED* 에러를 받아서 Jwt 토큰을 재발급 하는 요청을 실행
@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
if (requestURI.equals("/api/reToken")) {
filterChain.doFilter(request, response);
return;
}
String authorization = null;
Cookie[] cookies = request.getCookies();
// 쿠키의 유무
if (cookies == null) {
filterChain.doFilter(request, response);
return;
}
// 쿠키에서 access token을 추출
for (Cookie cookie : cookies) {
if (cookie.getName().equals("authorization")) {
authorization = cookie.getValue();
}
}
// access token 유무
if (authorization == null) {
log.info("token is null");
filterChain.doFilter(request, response);
return;
}
try {
jwtUtil.isExpired(authorization);
} catch (ChatException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Token이 만료되었습니다.");
return;
}
String username = null;
try {
username = jwtUtil.getUsername(authorization);
} catch (ChatException e) {
throw new ChatException(HttpStatus.BAD_REQUEST, ErrorCode.ACCESSTOKEN_IS_EXPIRED);
}
String role = jwtUtil.getRole(authorization);
Member member = Member.builder()
.username(username)
.role(Role.valueOf(role))
.build();
Map<String, Object> attributes = new HashMap<>();
CustomOAuth2User customOAuth2User = new CustomOAuth2User(member, attributes);
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
String requestURI = request.getRequestURI();
if (requestURI.equals("/api/reToken")) {
filterChain.doFilter(request, response);
return;
}
재발급 하는 요청 또한 만료된 Jwt 토큰을 사용하기에 401 에러가 발생하게 되는데
, 이를 필터에서 그냥 넘기기 위해 위 코드 작성
@PostMapping("/api/reToken")
public ResponseEntity<?> reissueController(@RequestHeader("refreshAuthorization") String refreshAuthorization, HttpServletRequest request, HttpServletResponse response) {
log.info("access token 재발급 시도 중 ..");
return reissueService.reissue(refreshAuthorization, request, response);
}
@Slf4j
@RequiredArgsConstructor
@Service
public class ReissueService {
private final JwtUtil jwtUtil;
private final StringRedisTemplate redisTemplate;
public ResponseEntity<?> reissue(String refreshAuthorization, HttpServletRequest request, HttpServletResponse response) {
String refresh_token = refreshAuthorization.substring(7);
try {
jwtUtil.isExpired(refresh_token);
} catch (ChatException e){
throw new ChatException(HttpStatus.BAD_REQUEST, ErrorCode.REFRESHTOKEN_IS_EXPIRED);
}
String category = jwtUtil.getCategory(refresh_token);
if (!category.equals("refresh")) {
throw new ChatException(HttpStatus.BAD_REQUEST, ErrorCode.IS_NOT_REFRESHTOKEN);
}
String username = null;
try {
username = jwtUtil.getUsername(refresh_token);
} catch (ChatException e) {
throw new ChatException(HttpStatus.BAD_REQUEST, ErrorCode.ACCESSTOKEN_IS_EXPIRED);
}
String role = jwtUtil.getRole(refresh_token);
String new_jwt = jwtUtil.createJwt("access", username, role, 1800000L); // 30분, 1800000L
String new_refresh = jwtUtil.createJwt("refresh", username, role, 259200000L); // 3일
redisTemplate.opsForValue().set(
"refresh_token:" + username,
new_refresh,
259200000L,
TimeUnit.MILLISECONDS
);
log.info("새로운 Access 토큰: " + new_jwt);
Cookie new_refresh_token = CookieUtil.createDefaultCookie("refresh_authorization", new_refresh);
response.setHeader("authorization", new_jwt);
response.addCookie(new_refresh_token);
return new ResponseEntity<>(HttpStatus.OK);
}
}
