반응형

다시 돌아가서
- 인증방식에 대해 다시 생각해보자.
- http 통신은 header에 요청에 대한 정보(인증 토큰)가 들어간다.
- jwt방식은 서버가 클라이언트로 부터 받은 ID와 Password가 맞는지 확인하고 맞으면 JSON Web Token이라는 긴 문자열을 브라우저에 전송해준다.
- JWT의 TOKEN을 만들기 위해서는 Header, Payload, Verify Signature 이렇게 세 가지가 필요하다.
- Header : 이 세 가지 정보를 암호화 할 방식, 타입 등이 들어간다.
- Payload : 서버에서 보낼 데이터가 들어간다. 일반적으로 유저의 고유 ID 값, 유효기간 등이 들어간다.
- Verify Signature : Base64 방식으로 인코딩한 Header, Payload 그리고 SECRET KEY를 더한 후 서명된다.
- 그래서 최종적으로는 : Encoded Header + "." + Encoded Payload + "." + Verify Signature가 된다.
- 그럼 클라이언트는 이 JWT를 쿠키나 로컬스토리지에 저장하게 하고
- 로그인 되어 있는 유저가 인증이 필요한 페이지를 클릭하면 자동으로 JWT를 헤더에 담아 서버로 보내게되고 서버는 이게 JWT가 있는지와 유통기한을 검사한 후 그에 맞는 필요한 정보를 전달해준다.
- 다만, 이미 발급 된 JWT는 돌이킬 수 없다.
- 그러니 악의적인 사용자는 유효기간 안에서 신나게 정보를 털어낼 수 있는 것이다.
- 이를 방지하기 위해서 기존 Access Token 유효기간을 짧게 설정하고 Refresh Token이라는 새로운 토큰을 발급한다.
- 그러면 Access Token을 탈취당해도 상대적으로 피해를 줄일 수 있을 것이다.
그럼 어떻게 refresh token을 구현할까?
- 구현사항을 정의해보자.
- 로그인 했을시 서버로부터 access, refresh token을 받고 localStorage에 저장해야한다.
- 매 요청마다 헤더에 access token을 넣어 서버로 보내야 한다.
- 또한 매 요청마다 이 access token의 만료기한이 지났는지 확인하고 지났다면
- 서버와 약속된 api로 refresh 토큰을 보낸 다음
- access token을 새롭게 받고
- 받은 access을 localStorage에 저장하고
- 헤더에 담아 서버로 보내 원래의 요청을 성공한다.
- 좋다! 사실 글이 많아보여서 그렇지 어렵지 않다.
- 항상 기억해두면 좋은건 결국 우리가 하는 것은 로켓발사알고리즘이 아니며
- 어려운것은 익숙하지 않아서이다.
매 요청마다
- api를 호출하다보면 그런 생각이 든다.(사실 나는 안들었었고.. 레퍼런스에 있길래 쓰면서 시작했다.. 필요해서 → 쓴다가 아닌, 처음부터 쓰니까 필요한지 몰랐다..)
- 매번 axios 요청할때마다, 겹치는 부분을 기본 URL로 설정하고 싶다.
- axios 사용할때마다 헤더를 매번 넣고 싶지 않다.
- 에러가 발생했을때 공통으로 처리하고 싶다.
- 이런 경우 interseptors를 사용하면 된다.
1) 정의
인터셉터는 1.요청하기 직전, 2. 응답을 받고 then, catch로 처리 직전에 가로챌 수 있다.
2) 구성
크게 3가지 부분으로 구성된다.
- 인스턴스
- request 설정
- response 설정
그리고 request, response 설정은 각각 2개의 콜백함수를 받는다.
import axios from 'axios'
// axios 인스턴스를 생성합니다.
const instance = axios.create({
baseURL: 'https://api.hnpwa.com',
timeout: 1000
});
/*
1. 요청 인터셉터
2개의 콜백 함수를 받습니다.
*/
instance.interceptors.request.use(
function (config) {
// 요청 성공 직전 호출됩니다.
// axios 설정값을 넣습니다. (사용자 정의 설정도 추가 가능)
return config;
},
function (error) {
// 요청 에러 직전 호출됩니다.
return Promise.reject(error);
}
);
/*
2. 응답 인터셉터
2개의 콜백 함수를 받습니다.
*/
instance.interceptors.response.use(
function (response) {
/*
http status가 200인 경우
응답 성공 직전 호출됩니다.
.then() 으로 이어집니다.
*/
return response;
},
function (error) {
/*
http status가 200이 아닌 경우
응답 에러 직전 호출됩니다.
.catch() 으로 이어집니다.
*/
return Promise.reject(error);
}
);
- 그렇다면 우리의 과정에서 이 interseptors를 어떻게 활용하면 좋을까?
- axios 인스턴스를 생성한다.
// axios 기본 주소 & header 타입 세팅
export const instance = axios.create({
baseURL: process.env.REACT_APP_AUTH_SERVER,
timeout: 5000,
});
- request를 설정한다.
//┏----------interceptor를 통한 header 설정----------┓
instance.interceptors.request.use(async config => {
config.headers["Content-Type"] = "application/json; charset=utf-8";
config.headers["X-Requested-With"] = "XMLHttpRequest";
config.headers["Accept"] = "*/*";
const accessToken = localStorage.getItem("accessToken");
if (!accessToken) return config;
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
});
- 이렇게 하면 매 요청마다 자체적으로 로컬스토리지에 accessToken이 있는지 확인하고
- 없으면 헤더를 그만 추가하고
- 있으면 헤더에 accessToken을 넣어서
- 모든 요청을 처리할 수 있다.
- 편리하다!
로그인시 token을 받고 localStorage에 저장
- 나는 redux-toolkit을 쓰고 있어서 createAsyncThunk를 썼지만
- 그것을 쓰지 않는다고해도 두번째 코드의 if 부분만 보면 핵심은 와닿을것이다.(아니면 어쩔수 없다..)
- 즉, 단순하게 서버로부터 받은 토큰 두가지를 localStorage.setItem을 이용해 로컬스토리지에 저장하면 된다.
- (localStorage.setItem는 DOM인가 BOM인가, BOM은 DOM안에 속하는가? 옛날에 렌더링과정 발표할때 잘정리했는데 헷갈린다.. 나중에 다시 알아봐야겠다.)
// 로그인 관련 axios API 통신
// loginSlice
export const loginApi = {
// 로그인
login: data => instance.post("/auth/signin", data),
};
// 로그인
export const __login = createAsyncThunk(
"post/login",
async (data, thunkAPI) => {
try {
const response = await loginApi.login(data);
console.log(response);
if (response.status === 201) {
localStorage.setItem("accessToken", response.data.member.accessToken);
localStorage.setItem("refreshToken", response.data.member.refreshToken);
window.location.replace("/signupFinish");
return response.data;
}
} catch (err) {
console.log(err);
return thunkAPI.rejectWithValue(err.response.data);
}
},
);
요청마다 token의 만료기한을 확인하고 지났다면..
- 이제는 핵심인 refreshToken이 나올차례이다.
- 뭐 막 이리저리 적었지만 까고보면 별 거 없다.
- 핵심은 react-jwt의 isExpired를 이용해서 만료를 확인하고
- 만료라면 서버와 약속된 api로 미리 받은 refreshToken을 보내고 다시 accessToken을 받은 이후, 저장하고, 다시 보내면 된다.
import { isExpired } from "react-jwt";
//┏----------interceptor를 통한 header 설정----------┓
instance.interceptors.request.use(async config => {
config.headers["Content-Type"] = "application/json; charset=utf-8";
config.headers["X-Requested-With"] = "XMLHttpRequest";
config.headers["Accept"] = "*/*";
const accessToken = localStorage.getItem("accessToken");
if (!accessToken) return config;
//////////////////추가된부분 시작//////////////////
if (isExpired(accessToken)) {
try {
const refreshToken = localStorage.getItem("refreshToken");
const response = await axios.post(
process.env.REACT_APP_AUTH_SERVER + `/auth/token`,
{ refreshToken },
);
localStorage.setItem("accessToken", response.data.member.accessToken);
// 12/21 추가 시작
localStorage.setItem("refreshToken", response.data.member.refreshToken);
// 12/21 추가 끝
config.headers.Authorization = `Bearer ${response.data.member.accessToken}`;
return config;
} catch (error) {
console.log(error);
localStorage.clear();
window.location.replace("/login");
return config;
}
}
//////////////////추가된부분 끝//////////////////
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
});
- 코드를 보면 바로 이해가 될 것이다.(아니라면..ㅠ)
- 혹시나 낯설 수 있는 부분은 interseptors중에 axios.post로 한번더 api호출을 한다는 점인데
- 이제 익숙해지면 된다.
- +) 12/21 추가 -> 액세스토큰이 만료되고 재발급을 받고 다시 액세스토큰이 만료되면 오류가 나는 이슈 확인했습니다. 원인은 리프레쉬토큰을 이용해 토큰을 재발급 받을때 액세스토큰만을 새롭게 업데이트해서였으며 이제 리프레쉬토큰도 매순간 업데이트하기에 해당 오류는 나지 않습니다.
끝내며
- 이렇게 refreshToken정리를 끝냈다.
- 항해때는 진짜 어렵고 복잡해보였는데
- 실타래를 하나씩 풀어보니까
마냥 어렵지만도 않은 것 같다.(?) - 다음에는 OAuth - kakao login을 하면 좋을 것 같다 :)
출처
https://velog.io/@skyepodium/axios-인터셉터로-API-관리하기
https://inpa.tistory.com/entry/AXIOS-📚-설치-사용
https://patrick-f.tistory.com/13
https://velog.io/@___pepper/React-OAuth-2.0-사용하기-refresh-token-grant
https://onelight-stay.tistory.com/278
https://velog.io/@gth1123/쿼리-스트링Query-string-URL-파라미터
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date/now
http://daplus.net/javascript-axios에서-헤더와-옵션을-설정하는-방법은-무엇입니/
https://jacobgrowthstory.tistory.com/44
https://velog.io/@bigbrothershin/Axios-delete-요청-시-body에-데이터-넣는-법
반응형
'develop' 카테고리의 다른 글
AWS S3로 프론트엔드(정적 웹사이트) 배포하기 (0) | 2023.01.20 |
---|---|
react-paginate를 이용한 pagination 구현 - 페이지마다 동적 api연결 (0) | 2023.01.20 |
axios란? (0) | 2023.01.20 |
Http, Session, JWT, OAuth (0) | 2023.01.20 |
프론트엔드가 이미지 색상을 실시간으로 바꾸는 법 - hex-to-css-filter (0) | 2023.01.20 |