[Flutter] dio 인터셉터

개발 기록·2024년 12월 23일

Flutter

목록 보기
17/18

❗️ jwt 토큰 로그인 구현 중인데, 토큰 만료시(401 에러) 재발급 로직을 탈 수 있도록 해야한다.

Why?

토큰 만료시 응답 값의 상태코드(401)를 받아 토큰 만료를 포착할 수 있지만, 관련하여 검색 해보니 dio의 인터셉터를 통해 401 발생시 토큰 재발급 로직을 엔드포인트로 이동하는 것을 많이들 사용하더라!

현재 dio 패키지로 네트워크 통신을 구현하고 있기 때문에 이를 사용안할 이유가 없고, 아무래도 인터셉터를 사용하는게 더 정확하고 효율적일 것 같다고 판단해서 구현해보았다.

또한, 인터셉터와 함께 dio 커스텀을 통해 base URL과, 헤더에 엑세스 토큰을 필수적으로 추가하는 등의 옵션을 함께 설정하여 코드의 효율성을 높일 수 있다고 생각한다.

구현

authDio라는 파일을 만들어서 그곳에 dio 인터셉터 로직을 작성했다.

InterceptorsWrapper를 통해 요청 전, 응답 후, 오류 발생시의 작업을 정의할 수 있는데 이 점이 node js의 app 설정에서 에러처리 미들웨어를 구현하는 것과 비슷하다고 느꼈다. 그래서 좀 친숙한 느낌 ㅎㅎ
(참고로 응답 후는 해당 과정에서 사용하지 않음! onResponse를 사용해서 응답 후의 코드 지정 가능함)

작성한 코드를 좀 뜯어서 기록을 해보겠다.

기존 인터셉터 제거

dio.interceptors.clear();
  • Dio 객체에 기존에 설정된 모든 인터 셉터 제거

인터셉터 추가

dio.interceptors.add(InterceptorsWrapper(...));
  • InterceptorsWrapper를 통해 요청 전, 응답 후, 오류 발생 시 추가 작업을 수행할 로직을 정의

요청 전 처리(onRequest)

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);
}));
  • 저장된 ACCESS_TOKEN을 가져옴
  • 기본 URL 설정
  • 헤더에 토큰 추가하여 액세스 토큰을 인증 정보에 포함
  • handler.next(options)를 호출하여 요청을 서버로 전송

응답 오류 처리(onError)

  // 응답 오류가 발생했을 때 처리하는 부분
      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 상태 코드(인증 실패) 발생하면 아래 과정 수행
  • 리프레시 토큰을 사용하여 새로운 액세스 토큰 발급 (/reissue)
  • 새로운 액세스 토큰 저장 및 재요청
  • 기존 요청에 새로운 토큰 추가 후, 동일 요청 다시 시도
  • 리프래쉬 토큰 없으면 로그아웃 처리

실행흐름 요약

  1. 네트워크 요청시 authDio를 통해 설정된 Dio객체 생성
  2. 요청 전 헤더에 액세스 토큰을 추가해 인증 처리
  3. 응답에서 오류가 발생하면,
  • 401 상태의 경우 리프레쉬 토큰으로 새 액세스 토큰을 받아 요청 재시도
  • 리프레쉬 토큰이 없으면 로그아웃 처리
  1. 설정이 완료된 Dio를 반환해 네트워크 요청에 사용

0개의 댓글