티스토리 뷰

교육을 들을 수 있는 기회가 생겼다..!!

현재 진행중인 프로젝트에서 관리하고 있는 방법과 배우고 나서 어떻게 관리하면 좋을지 비교하고 수정하기 위해 작성해보겠다😆

 

진행중인 프로젝트에서 관리하고 있는 방법을 작성한 블로그

 

1. https://anywhereim.tistory.com/95

 

axiosInstance 작성해보기(accessToken, refreshToken)

이전 내용에 이어서 axiosInstance코딩한 내용을 작성해보고자 한다. fetch보다 사용성도 좋고 error 핸들링이 좋은 axios 알랍유야.. 리펙토링 되었습니다. 리펙토링은 간단한 코드분리 정도이므로 아

anywhereim.tistory.com

 

2. https://anywhereim.tistory.com/111

 

토큰 만료처리하기

프로젝트를 진행하면서 부족한 부분들이 생긴다.그래서 프로젝트를 하면서 중간중간 리펙토링도 많고 추가 해야하는 기능도 많아 두서없는 TIL을 쓰게 된다.. ㅎ쨋든. 📖AxiosInstance 이외에 토큰

anywhereim.tistory.com

 

3. https://anywhereim.tistory.com/112

 

axiosInstance 리펙토링하기

📖 AxiosInstance 리펙토링이 필요한 이유1. ` basicAxiosInstance `기본 axiosInstance 2. ` axiosInstance `accessToken을 헤더에 담고, accessToken이 만료된 경우 refreshToken을 이용해 accessToken 재발급요청을 해주는 axiosI

anywhereim.tistory.com

 

 

인가: 권한이 있는지 없는지 여부를 판단

인증: 로그인을 하는 행위 

 

개념정리

 

` accessToken `

목적:

1. accessToken은 사용자가 서버에 로그인 후 서비스를 이용할 수 있는 권한이 있는지 여부를 판단하는데 사용된다.

2. 서버에서는 서버에서 발급한 accessToken이 맞는지 확인 후 인가를 부여

3. accessToken 만료시간 측정

 

유효기간:

1. 보안을 강화하기 위해 accessToken의 유효기간은 몇분에서 몇 시간으로 짧게 유지

 

보안성:

1. accessToken이 노출되더라도 유효기간이 짧기 때문에 refreshToken 보다 보안 위협이 상대적으로 낮다.

 

 

`refreshToken`

목적:

1. refreshToken은 accessToken의 유효기간이 만료된 후 새로운 accessToken을 발급받기 위해 사용

2. accessToken 만료시간이 지나더라도 재발급을 통해 재로그인을 하지 않도록 함

 

유효기간:

1. accessToken에 비해 긴 시간동안 유효하다. 

 

보안성:

1. refreshToken이 유출될 경우 사용자의 권한을 오랫동안 남용할 수 있게되므로 높은 보안을 필요로 한다.

 

상단에 현재 사용중인 관리 방법을 작성한 블로그 경로를 걸어두었는데 

내가 작성한 코드가 관리 방법인지에 대한 의문이 들었다. 

그래서 한번에 볼 수 있도록 글로써 작성해보고자 한다.

 

 

작동 방식과 함께 관리 방법을 함께 풀어보자(         이 관리 방법)

 

작동 방식과 관리 방법

1. 사용자가 로그인 요청 시 응답값으로 accessToken과 헤더에 refreshToken이 담긴 쿠키를 담아서 받는다.

2. 이때 로그인 요청과 동시에 응답값으로 받은

- accessToken을 "token" 이라는 이름으로 localstorage에 담고 

- 디코딩 후 디코딩된 값을 "user"라는 이름으로 zustand parsist를 이용해 localstorage에 담는다.

 

 

"

 

❓localstorage를 선택한 이유

- 브라우저 창이 닫힐 경우 삭제

- 백앤드에서 header에 직접적으로 token을 담아서 보낼 것을 요구

 

 

❓ 그렇다면 여기에서 백앤드는 왜 header에 직접적으로 token을 담아서 보낼 것을 요구하였는가

⭐`Bearer` token 정책⭐

OAuth2.0 인증 프레임워크에서 사용되는 방식으로 accessToken을 Http 요청의 `Authorzation` 헤더에

`Bearer` 이라는 타입으로 포함시켜 서버에 전송하는 것을 의미한다. 

이 방식은 accessToken을 사용해 api 요청이 이증되는 방식이다.

 

- 보안성: 

accessToken이 직접 헤더에 포함되어 있어 cookie를 사용하는 것에 비해 특정 도메인에 종속되지 않고, 

cookie를 통한 cross-site request forgery 공격에 덜 취약하다.

 

- 명확성과 간단함:

클라이언트는 token을 헤더에 명확하게 포함시켜 요청을 보내므로 서버는 요청을 받알쓸때 헤더에서 바로 token을 읽어 인증을 처리할 수 있다.

"

 

 

3. 3개의 axiosInstance를 만들어 api를 요청

 

✅refreshToken 요구

- accessToken을 재발급 받기 위한 개별 axiosInstance (axios.defaults.withCredentials = true)

 

✅  accessToken 요구

- accessToke을 필요로 하는 경우 header에 accesstoken을 필요로 하는 api를 요청하는 axiosInstace

 

✅  요구 사항 없음

- accessToken을 필요로 하지 않는 api를 요청하는 경우 axiosInstace (axios.defaults.withCredentials = false) 

 

 

< refreshToken 요구 > accessToken 재발급

import { decodeTokenOnServer } from "./decode/decodedAxios";
import { requests } from "./request";
import axios from "axios";

axios.defaults.withCredentials = true;

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "json",
  withCredentials: true,
});

export const getNewAccessToken = async () => {
  try {
    const response = await axiosInstance.post(
      `${requests.getNewAccessToken}`,
      {},
      {
        withCredentials: true,
      }
    );
    const newAccessToken = response.data.data.accessToken;

    await decodeTokenOnServer(newAccessToken);

    localStorage.setItem("token", newAccessToken);
    return newAccessToken;
  } catch (error: any) {
    alert("로그인 정보가 만료되어 로그인이 필요합니다.");
    localStorage.removeItem("token");
    localStorage.removeItem("user");
    window.location.href = "/users/login";
    return null;
  }
};
  •  cookie에 있는 refreshToken을 함께 전달. 
  • 재발급 받은 accessToken을 디코딩 하는 함수에 전달
  • refreshToken 만료로 인해 error를 반환 받는 경우 localStorage에 저장된 값 삭제 및 로그인 페이지로 리다이렉트
  • 로그인 api 요청 시 instance는 꼭  withCreadential: ture 설정이 되어있는 axios instance를 사용해주어야한다! 그래야 header로 받은 쿠키가 저장이 된다.

 

< accessToken 요구 > header에 token 설정

import axios from "axios";
import { getNewAccessToken } from "./withCredentials";

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "json",
  withCredentials: false,
});

// 요청 인터셉터 설정
axiosInstance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("token");
    if (token) {
      config.headers["Authorization"] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error.message);
  }
);

// 응답 인터셉터 설정
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    //중복 요청 방지
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      const newAccessToken = await getNewAccessToken();

      if (newAccessToken) {
        // 원래 요청의 헤더를 새로운 토큰으로 업데이트
        originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
        return axiosInstance(originalRequest);
      }
    }
    return Promise.reject(error);
  }
);

export default axiosInstance;

 

  • api 요청 시 accessToken으로 가지고 api 요청
  • accessToken 만료로 인해 error 발생 시 `getNewAccessToken` 를 호출
  • accessToken 재발급 성공 시 api 재요청

 

< 요구사항 없음 >

import axios from "axios";

const basicAxiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "json",
  withCredentials: false,
});

export default basicAxiosInstance;
  • 인가가 필요 없는 api 요청 시 사용

 

4. token 만료처리

api요청이 필요한 페이지로 넘어가는 버튼을 클릭할 때 refreshToken 만료를 확인하여 api요청 시 로그인페이지로 리다이렉트 되는 경우를 방지 

** api 요청이 필요한 페이지로 넘어가는 버튼은 user의 정보가 있는 경우에만 보임

import { getNewAccessToken } from "@/app/api/withCredentials";

const timeStamp = () => {
  const getLoginUserInfo = localStorage.getItem("user");
  if (getLoginUserInfo) {
    const userObj = JSON.parse(getLoginUserInfo);
    const expValue = userObj.state.user.exp * 1000;

    const nowTime = new Date().getTime();
    return nowTime > expValue;
  } else{
    
  }

  return false;
};

export const expirationTime = async () => {
  if (timeStamp()) {
    await getNewAccessToken();
  }
};