인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

Link님의 프로필 이미지

작성한 질문수

코드로 배우는 React with 스프링부트 API서버

access Token 만료 후 todo/list 접속시도 에러 처리

해결된 질문

작성

·

52

0

안녕하세요.
access Token 정보 만료 후에 서버에서 보내는 메시지가 401 메시지로 옵니다.
그래서인지 jwtUtil.js 파일에서 처리를 못하는 문제가 있는 것이 맞을까요?

responseFail 을 아래와 같이 수정하는게 맞는지 무엇을 놓치고 있는 것인지 모르겠습니다.

http://localhost:3000/todo/list 를 10분 이상 경과후 실행했을 때 에러 메시지를 출력했었습니다.

import axios from "axios";
import {getCookie, setCookie} from "./cookieUtil";
import {API_SERVER_HOST} from "../api/hostApi";

const jwtAxios = axios.create()

const refreshJWT = async (accessToken, refreshToken) => {

    const host = API_SERVER_HOST

    const header = {headers: {"Authorization": `Bearer ${accessToken}`}}

    const res = await axios.get(`${host}/api/member/refresh?refreshToken=${refreshToken}`, header)

    console.log("----------------------")
    console.log(res.data)

    return res.data
}


//before request
const beforeReq = (config) => {
    console.log("before request.............")

    const memberInfo = getCookie("member")

    if (!memberInfo) {
        console.log("Member NOT FOUND")
        return Promise.reject(
            {
                response:
                    {
                        data:
                            {error: "REQUIRE_LOGIN"}
                    }
            }
        )
    }

    const {accessToken} = memberInfo

    // Authorization 헤더 처리
    config.headers.Authorization = `Bearer ${accessToken}`

    return config
}

//fail request
const requestFail = (err) => {
    console.log("request error............")
    return Promise.reject(err)
}

//before return response
const beforeRes = async (res) => {
    console.log("before return response...........")

    console.log(res)

    //'ERROR_ACCESS_TOKEN'
    const data = res.data

    if (data && data.error === 'ERROR_ACCESS_TOKEN') {

        const memberCookieValue = getCookie("member")

        const result = await refreshJWT(memberCookieValue.accessToken, memberCookieValue.refreshToken)
        console.log("refreshJWT RESULT", result)

        memberCookieValue.accessToken = result.accessToken
        memberCookieValue.refreshToken = result.refreshToken

        setCookie("member", JSON.stringify(memberCookieValue), 1)

        //원래의 호출
        const originalRequest = res.config
        originalRequest.headers.Authorization = `Bearer ${result.accessToken}`

        return await axios(originalRequest)

    }

    return res
}


//fail response
const responseFail = async (err) => {
    console.log("response fail error.............")
    console.log(err)

    const originalRequest = err.config;

    // 토큰 만료로 인한 401 응답이라면 → refresh 시도
    if (
        err.response &&
        err.response.status === 401 &&
        !originalRequest._retry // 무한 루프 방지
    ) {
        originalRequest._retry = true;

        const memberCookieValue = getCookie("member");

        try {
            const result = await refreshJWT(
                memberCookieValue.accessToken,
                memberCookieValue.refreshToken
            );

            // 토큰 저장
            memberCookieValue.accessToken = result.accessToken;
            memberCookieValue.refreshToken = result.refreshToken;
            setCookie("member", JSON.stringify(memberCookieValue), 1);

            // 원래 요청 재시도
            originalRequest.headers.Authorization = `Bearer ${result.accessToken}`;
            return await axios(originalRequest);

        } catch (refreshError) {
            console.error("Refresh 실패, 로그아웃 처리 필요");
            // 로그아웃 로직 연결 가능
            localStorage.removeItem("token");
            return Promise.reject(refreshError);
        }
    }

    return Promise.reject(err);
}

jwtAxios.interceptors.request.use(beforeReq, requestFail)

jwtAxios.interceptors.response.use(beforeRes, responseFail)

export default jwtAxios

답변 3

0

구멍가게코딩단님의 프로필 이미지
구멍가게코딩단
지식공유자

JWTCheckFilter에서 401에러를 만들어서 보내시는 부분이 있네요

 

본인이 작성하신 코드

} catch (Exception e) {

log.error("JWT Check Failed: {}", e.getMessage());

// 4. 인증 실패 응답 세팅

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

response.setContentType("application/json");

String msg = new Gson().toJson(Map.of("error", "ERROR_ACCESS_TOKEN"));

try (PrintWriter writer = response.getWriter()) {

writer.println(msg);

}

// 5. 인증 정보 초기화 (중요)

SecurityContextHolder.clearContext();

}

--------------------------------

예제 코드

}catch(Exception e){

log.error("JWT Check Error..............");

log.error(e.getMessage());

Gson gson = new Gson();

String msg = gson.toJson(Map.of("error", "ERROR_ACCESS_TOKEN"));

response.setContentType("application/json");

PrintWriter printWriter = response.getWriter();

printWriter.println(msg);

printWriter.close();

}

Link님의 프로필 이미지
Link
질문자

감사합니다.

제가 Spring Boot 은 이제 배우는 초보라서 아는게 턱없이 부족합니다.

잘 동작하는 거 확인했습니다.

0

구멍가게코딩단님의 프로필 이미지
구멍가게코딩단
지식공유자

음.. 좀 이상하네요..

서버의 JWTCheckFilter의 경우 accessToken을 검증한 후에 문제가 발생하면 catch로 빠지게 됩니다.

그런데 catch에서는 보내는 메세지는 그냥 200 번 상태 코드이고 'ERROR_ACCESS_TOKEN'으로 나가는게 맞습니다.

 

그러니 리액트 코드에서는 responseFail 에 걸리는게 아니라 beforeRes에 걸려야만 합니다.

 

제가 소스 코드를 실행해서 확인해 봐야 정확히 말씀드릴 수 있을 듯 합니다.

cookie_00@naver.com으로 소스 코드를 실행할 수 있는 링크나 방법등을 알려주시면 실행해서 원인을 좀 더 살펴보겠습니다.

 

 

 

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. Link님.

Access Token이 만료된 후 401 오류가 발생하며, 이로 인해 jwtUtil.js에서 적절히 처리되지 않는 상황을 겪고 계시는군요. jwtAxios 인터셉터 설정에 대해 함께 살펴보고 개선할 수 있는 부분을 안내해 드리겠습니다.

Access Token 처리 및 Refresh Token 갱신 흐름

기존 코드에서는 401 상태 코드가 반환될 때, 새로운 Access Token을 획득하기 위해 Refresh Token을 사용하여 토큰 갱신을 시도합니다. 이를 위해 responseFail과 beforeRes에 관련 로직이 포함되어 있습니다.

개선점 제안

  1. Refresh Token 호출 검사: refreshJWT 함수에서 사용되는 URL 및 헤더 설정 등이 올바른지 한번 더 확인해 보세요. 특히 헤더의 Authorization 설정이 정확한지 여부가 중요합니다.

  2. 무한 루프 방지: originalRequest._retry 플래그를 사용하여 무한 루프를 예방하고 있으므로, 이 부분이 잘 작동하고 있는지 확인하세요.

  3. 로그아웃 로직 강화: Refresh Token이 손상되거나 실패할 경우 사용자를 로그아웃 시키는 로직을 준비해두는 것이 좋습니다. 이는 사용자 경험을 안정적으로 유지하는 데 도움을 줄 수 있습니다.

  4. 추가적인 사용자 안내: Refresh Token 갱신 과정이나 오류 발생 시 사용자에게 명확한 안내(알림 메시지 등)를 제공하여 혼란을 줄일 수 있습니다.

토큰 처리의 예외 상황

  • 서버로부터 오는 오류 메시지가 정확히 “ERROR_ACCESS_TOKEN”인지, 401 상태 코드에 대해 적절히 대처하고 있는지 먼저 확인합니다.
  • 토큰 만료 시 서버에서 응답 방식이 강의 예제와 동일한지, 커스터마이징이 필요한지 검토해보세요.

관련하여 참고할 수 있는 질문과 답변 URL을 추천해 드립니다. 이들 게시물에서는 토큰 만료 관련된 유사한 문제와 해결책을 논의하고 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

Link님의 프로필 이미지

작성한 질문수

질문하기