티스토리 뷰
Programming language/vuejs
axios interceptors 를 활용한 vue + ts + jwt ( token/refreshToken )정리
hello-world 2022. 5. 25. 17:41728x90
반응형
axios interceptors 를 활용한 vue + ts + jwt ( token/refreshToken )정리
axios 라이브러리의 interceptors 를 이용해 연동했다.
요청( interceptors.request )에서는 token 이 localStorage 에 존재하는 경우에만
headers.Authrization 에 token 을 담아 전송하도록 했다.
응답( interceptors.response ) 에서는 token 이 expire 되어서 갱신해야 하는 경우
refresh token 을 전송 및 token 을 갱신 혹은 만료되는 상황을 처리 하도록 했다.
상세한 부분은 각 코드에 주석으로 처리해 두었다.
jwtService.ts
import store from '@/store';
import router from '@/router';
import AuthConfig from '@/core/auth/config';
import { AxiosRequestConfig, AxiosResponse, AxiosStatic } from 'axios';
import { getToken } from '@/core/auth/utils';
import { AuthService } from '@/restApi';
import { UserMutationType } from '@/store/moduleType/AuthMutationTypes';
import { TokenActionType } from '@/store/moduleType/AuthActionTypes';
export default class JwtService{
private readonly axiosInstance: AxiosStatic;
//대기요청 상태인지 체크 toggle 변수
private isTokenRefreshCheck: boolean=false;
//콜백함수 타입의 배열
private refreshSubscribers: Array<(token: string)=>void>=[];
constructor(axiosInstance: AxiosStatic ) {
this.axiosInstance=axiosInstance;
/**
* request interceptor
*/
this.axiosInstance.interceptors.request.use( (config: AxiosRequestConfig)=>{
const token=getToken();
//토큰이 localstorage 에 있을 때만 header 에 토큰을 심어둔다.
if (token) {
//config.headers.Authorization 과 axios.defaults.headers.common.Authorization 은 서로 다르다.
config.headers.Authorization=`${AuthConfig.TOKEN_TYPE}${token}`;
}
return config;
}, (error: any)=>{
return Promise.reject( error );
} );
/**
* response interceptor
*/
this.axiosInstance.interceptors.response.use( (response: AxiosResponse)=>{
return response;
}, async (error: any)=>{
const { status, config, data }=error.response;
const responseConfig=config;
if(status === 401 ){
//로그인 실패시~
if (String( config.url ).includes( 'auth/login' ) && data.code === 700) {
store.commit( `Auth/${ UserMutationType.LOG_IN }`, false );
}else if(data.code===611){
if (!this.isTokenRefreshCheck) {
// isTokenRefreshing 이 false 인 경우에만 token refresh 요청
this.isTokenRefreshCheck=true;
// refresh token 요청
AuthService.fetchRefreshToken().then( (res: any)=>{
this.isTokenRefreshCheck=false;
const { accessToken, refreshToken }=res;
this.setTokens( accessToken, refreshToken );
setTimeout( async ()=>{
// 새로운 토큰으로 지연되었던 요청 진행
await this.getTokenRefreshed( accessToken );
//저장 배열 초기화
await this.removeRefreshSubscribers();
}, 700 );
} ).catch( (error: any)=>{
const { code, message, status }=error.data;
// console.log( error, code, message );
// refresh token 정보도 만료 되었을 때 로그인 페이지로 보낸다.
if (status === 401 && message === 'token expired') {
alert( '사용자 정보가 만료되었습니다.\\n 다시 로그인 해주세요' );
//로그아웃
this.shouldUnAuthorized();
}
} );
}
// token 이 재발급 되는 동안의 요청은 refreshSubscribers 에 저장
return new Promise( (resolve)=>{
//getTokenRefreshed 에서 전달된 token 을 내부에서 refreshSubscribers( 콜백함수 저장한 배열 ) 를 forEach 로 순환 대입( 전달된 token) 실행시킨다.
this.addRefreshSubscriber( (token: string)=>{
responseConfig.headers.Authorization=`${ AuthConfig.TOKEN_TYPE }${ token }`;
resolve( this.axiosInstance( responseConfig ) );
} );
} );
}else if (data.code === 613) { //613 : 갱신 토큰 만료 >> '화면 이동 (로그인)'
alert( '사용자 정보가 만료되었습니다. 로그인 해 주세요~' );
//로그아웃 시키기.
await this.shouldUnAuthorized();
}else if (data.code === 612) {
alert( '인증되지 않은 사용자 입니다. 로그인 해 주세요~' );
await this.shouldUnAuthorized();
}
}
// Do something with response error
return Promise.reject( error );
} );
}
/**
* 새로 발급 받는 token 재지정.
* @param token
* @param refreshToken
*/
public async setTokens( token: string, refreshToken: string){
await store.dispatch( `Auth/${ TokenActionType.SIGN_IN_BY_TOKEN }`, { token, refreshToken } );
};
/**
* 콜백함수 타입의 배열 초기화
*/
private removeRefreshSubscribers(){
this.refreshSubscribers=[];
};
/**
* 실행 콜백함수 배열 대입.
* @param callback
*/
private addRefreshSubscriber( callback: (token: string)=> void){
this.refreshSubscribers.push( callback );
};
/**
* 배열에 저장된 콜백함수( addRefreshSubscriber ) 실행.
* @param token
*/
private getTokenRefreshed(token: string){
this.refreshSubscribers.forEach( (callback: (token: string)=>void)=>callback( token ) );
};
/**
* 로그아웃 시키기
*/
private shouldUnAuthorized(){
///login?rPath=${encodeURIComponent(location.pathname)}
store.commit( `Auth/${ UserMutationType.LOGOUT }` );
router.push( { path: '/login', query:{rPath:new Date().getTime().toString()}} )
.then( ()=>{
console.log( '로그아웃' );
} );
}
}
728x90
반응형
'Programming language > vuejs' 카테고리의 다른 글
[vite] vite 로 vue3 번들링시 .env 환경변수 설정 (1) | 2023.01.27 |
---|---|
vue+ts 유용한 Utils ( js 에서 ts 변환 ) (0) | 2022.05.30 |
vue 정리 - login (로그인) part2 (0) | 2022.05.19 |
vue 정리 part3 - Vue-router (0) | 2022.05.17 |
vue 정리 - login(로그인) part1 (0) | 2022.05.06 |
댓글
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 앵귤러
- 아이콘 폰트 만들기
- 내장요소
- 반복문
- 리프래시토큰
- cordova
- JsDoc
- Angular
- 태그
- anime.js
- git checkout -b
- Vue3
- git
- Intrinsic
- RefreshToken
- React.StrictMode
- react-router-dom
- for of 구문
- svg icon font
- icon font
- Aptana
- react
- svg모션
- vue-router
- IntrinsicElements
- CSS
- interceptors
- 자바스크립트
- svg 폰트
- 코도바
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함