5. AccessToken이 만료되었을때 - 만료응답을 클라이언트에 반환
//토큰이 유효하지 않을 경우
if (!jwtService.validateToken(jwt, userDetails.getUsername())) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access token expired");
return;
}
만료된 토큰을 받은 서버는 HTTP상태 코드와 함께 "Access token expired"메세지를 클라이언트에 반환
6. AccessToken이 만료되었을때 - 만료응답을 받은 클라이언트
api.interceptors.response.use(
response => response,
async error => {
if(error.response.status === 401 && error.response.data === "Access token expired"){
console.log("토큰 만료");
const refreshToken = document.cookie.replace(
/(?:(?:^|.*;\s*)refresh_token\s*\=\s*([^;]*).*$)|^.*$/,
"$1"
); // 쿠키에서 Refresh Token을 가져옴(정규식)
const accessToken = localStorage.getItem('token'); // access Token
// refresh, access token을 서버로 보내서 재발급 받은 acccessToken 저장
const newAccessToken = await refreshTokenAxios(accessToken, refreshToken);
// 실패했던 요청을 다시 시도
error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
return axios(error.config);
}
return Promise.reject(error);
}
);
만료 응답을 받은 클라이언트는 서버로부터 access token을 새로 발급받아야한다.
이때 쿠키에 저장된 refresh token을 가져와서 access token과 함께 서버로 보낸다.
7. AccessToken이 만료되었을때 - 재발급을 위한 refresh, access token을 받은 서버
public String refreshAccessToken(String accessToken, String refreshToken) {
// Access token 정보
String userEmailFromAccess = extractUsername(accessToken);
Long userIdFromAccess = extractUserId(accessToken);
// DB에 refreshToken이 저장되어있는지 확인
User user = userRepository.findByRefreshToken(refreshToken)
.orElseThrow(() -> new IllegalArgumentException("User not found"));
// 같은 사용자의 token인지 확인
if(!userEmailFromAccess.equals(user.getEmail()) || !userIdFromAccess.equals(user.getUserId())) {
throw new IllegalArgumentException("Tokens do not match for the same user");
}
// refresh token 만료 확인
if(isTokenExpired(refreshToken)) {
throw new IllegalArgumentException("Refresh token is expired");
}
return Jwts.builder()
.setSubject(user.getEmail())
.claim("role", user.getUserType())
.claim("userId", user.getUserId())
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE_TIME))
.signWith(key)
.compact();
}
1. 서버는 클라이언트로부터 refresh token과 access token을 받는다.
2. access token에서 사용자 이메일, id를 추출한다.
3. refresh token을 이용해 DB에서 해당 사용자가 저장되어있는지 확인한다.
-> 일치하는 사용자가 없다면 예외발생
4. access token에서 추출한 정보와 refresh token의 정보가 일치하는지 확인한다.
-> 사용자 정보가 일치하지않는다면 예외발생
5. 사용자 정보까지 일치했다면 refresh token의 만료여부를 검사한다.
-> refresh token이 만료되었다면 예외발생
6. 만약 모든 단계를 통과했다면 access token 재발급!
컨트롤러에서의 예외처리
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) {
try {
String newAccessToken = jwtService.refreshAccessToken(refreshTokenRequestDTO.getAccessToken(), refreshTokenRequestDTO.getRefreshToken());
return ResponseEntity.ok(Map.of("access_token", newAccessToken));
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Refresh Token is expired")) {
return ResponseEntity.status(401).body("Refresh Token expired");
} else if (e.getMessage().equals("Tokens do not match for the same user")) {
return ResponseEntity.status(403).body("Tokens do not match for the same user");
} else {
return ResponseEntity.status(400).body("Invalid Token");
}
}
}
상황 | 예외 메시지(응답 메시지) | HTTP 상태 코드 |
refresh token이 만료 | Refresh Token is expired | 401(Unauthorized) |
access, refresh token 사용자 불일치 | Tokens do not match for the same user | 403(Forbidden) |
기타(유효하지 않은 토큰) | Invalid Token | 400(BadRequest) |
8. AccessToken이 만료되었을때 - Access Token 재발급
try {
const response = await api.post("/api/token/refresh", { accessToken, refreshToken });
const newAccessToken = response.data.access_token;
localStorage.setItem("token", newAccessToken);
error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
return api(error.config);
} catch (refreshError) {
if (refreshError.response.status === 401 && refreshError.response.data.message === "Refresh token is expired") {
// 로그아웃 처리 및 로그인 페이지로 리다이렉트
handleLogout();
} else {
console.error("토큰 재발급 중 오류 발생:", refreshError);
return Promise.reject(refreshError);
}
}
refresh도 정상 상태라면 정상적으로 accesstoken을 재발급 받은 후 실패한 요청 처리
만약에 refresh token이 만료된 상태라면 로그인페이지로 리다이렉트 및 모든 데이터 로그아웃 처리!
++ cookie는 서버에서 지우는거였음..?
// 쿠키 삭제를 위한 설정
Cookie refreshTokenCookie = new Cookie("refresh_token", null); // 쿠키의 값을 null로 설정
refreshTokenCookie.setPath("/"); // 쿠키의 경로를 지정
refreshTokenCookie.setMaxAge(0); // 쿠키의 만료 시간을 0으로 설정하여 즉시 삭제
refreshTokenCookie.setHttpOnly(true); // HttpOnly 속성 설정
refreshTokenCookie.setSecure(true); // HTTPS 환경에서만 전송 (옵션)
response.addCookie(refreshTokenCookie); // 응답에 쿠키 추가 (삭제 요청)
이렇게 했는데 맞는건가....
시큐리티는 아무래도 따로 프젝파서 다시 연습해야할거같음....
그래도 어찌저찌 햇다..!!
'[ STUDY ] > Spring Boot' 카테고리의 다른 글
@Component, @Autowired (1) | 2024.11.28 |
---|---|
IOC / DI / AOP (2) | 2024.11.19 |
[ JWT를 활용한 Spring Security ] Access Token, Refresh Token 발급 및 저장 (0) | 2024.09.25 |
[ JWT를 활용한 Spring Security ] Refresh Token?Access Token? (0) | 2024.09.20 |
[ JWT를 활용한 Spring Security ] JWT 토큰 생성 (0) | 2024.09.20 |