❗️ jwt 토큰 로그인 구현 중인데, 토큰 만료시(401 에러) 재발급 로직을 탈 수 있도록 해야한다.
토큰 만료시 응답 값의 상태코드(401)를 받아 토큰 만료를 포착할 수 있지만, 관련하여 검색 해보니 dio의 인터셉터를 통해 401 발생시 토큰 재발급 로직을 엔드포인트로 이동하는 것을 많이들 사용하더라!
현재 dio 패키지로 네트워크 통신을 구현하고 있기 때문에 이를 사용안할 이유가 없고, 아무래도 인터셉터를 사용하는게 더 정확하고 효율적일 것 같다고 판단해서 구현해보았다.
또한, 인터셉터와 함께 dio 커스텀을 통해 base URL과, 헤더에 엑세스 토큰을 필수적으로 추가하는 등의 옵션을 함께 설정하여 코드의 효율성을 높일 수 있다고 생각한다.
authDio라는 파일을 만들어서 그곳에 dio 인터셉터 로직을 작성했다.
InterceptorsWrapper를 통해 요청 전, 응답 후, 오류 발생시의 작업을 정의할 수 있는데 이 점이 node js의 app 설정에서 에러처리 미들웨어를 구현하는 것과 비슷하다고 느꼈다. 그래서 좀 친숙한 느낌 ㅎㅎ
(참고로 응답 후는 해당 과정에서 사용하지 않음! onResponse를 사용해서 응답 후의 코드 지정 가능함)
작성한 코드를 좀 뜯어서 기록을 해보겠다.
dio.interceptors.clear();
dio.interceptors.add(InterceptorsWrapper(...));
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) async {
final accessToken = await storage.read(key: 'ACCESS_TOKEN');
options.baseUrl = "$BASE_URL/login";
options.headers['access'] = accessToken;
return handler.next(options);
}));
handler.next(options)를 호출하여 요청을 서버로 전송 // 응답 오류가 발생했을 때 처리하는 부분
onError: (error, handler) async {
// 401 오류(인증 실패)가 발생한 경우 처리
if (error.response?.statusCode == 401) {
final accessToken = await storage.read(key: 'ACCESS_TOKEN');
final refreshToken = await storage.read(key: 'REFRESH_TOKEN');
if (refreshToken != null) {
// 리프레시 토큰을 사용하여 새로운 액세스 토큰을 요청
var refreshDio = Dio();
refreshDio.options.baseUrl = BASE_URL; // 기본 URL 설정
final refreshResponse = await refreshDio.post(
'/reissue',
data: {"refresh": refreshToken}, // 리프레시 토큰을 서버에 전달
);
if (refreshResponse.statusCode == 200) {
// 새로운 액세스 토큰을 받아서 저장
final newAccessToken = refreshResponse.data["result"]['token'];
await storage.write(key: 'ACCESS_TOKEN', value: newAccessToken);
// 새로운 액세스 토큰을 요청에 추가
error.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
// 기존 요청을 새로운 액세스 토큰으로 다시 시도
final clonedRequest = await dio.request(error.requestOptions.path,
options: Options(
method: error.requestOptions.method,
headers: error.requestOptions.headers),
data: error.requestOptions.data,
queryParameters: error.requestOptions.queryParameters);
return handler.resolve(clonedRequest); // 수정된 요청으로 응답 처리
}
}
// 리프레시 토큰이 없으면 로그아웃 처리 후 로그인 화면으로 이동
await storage.deleteAll();
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => Login()));
}
// 오류가 계속 발생하면 오류를 그대로 전달
return handler.next(error);
}));
401 상태 코드(인증 실패) 발생하면 아래 과정 수행