인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

dev님의 프로필 이미지

작성한 질문수

[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!

Pagination UI에 적용해보기

auth 관련 에러

작성

·

75

0

7강 마지막 들으면서 적용하는데
분명 에뮬레이터도 정상동작인데 dio.dart의 onError함수에서 계속 401 auth에러가 발생한다고 표시가 돼서 콘솔로 디버깅을 했습니다.
그런데 마지막 resending하는 부분까지 정상 도달은 하지만 onResponse의 콘솔은 뜨지않고 재시작해서 accessToken을 다시 발급 받았음에도 계속 onError에서 401auth 에러가 발생합니다. 어떤 부분이 문제일까요

[요약]
토큰만료 =>홈 스크린에 데이터 사라짐 => 재시작 => 홈 스크린에 데이터 불러와짐 => 문제발생 데이터는 불러와지는데 디버그 콘솔에 REQ랑 ERROR표시남(401 AUTH 관련 에러) => 재시작 => 동일 에러 무한

[에러 내용]
I/flutter ( 4232): [REQ] [GET] http://10.0.2.2:3000/restaurant/

I/flutter ( 4232): [ERROR] [GET] http://10.0.2.2:3000/restaurant/

I/flutter ( 4232): [ERROR] [MESSAGE] This exception was thrown because the response has a status code of 401 and RequestOptions.validateStatus was configured to throw for this status code.

I/flutter ( 4232): The status code of 401 has the following meaning: "Client error - the request contains bad syntax or cannot be fulfilled"

I/flutter ( 4232): Read more about status codes at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

I/flutter ( 4232): In order to resolve this exception you typically have either to verify and fix your request code or you have to fix the server code.

dio.dart

import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:section2/common/const/data.dart';

class CustomInterceptor extends Interceptor {
  final FlutterSecureStorage storage;

  CustomInterceptor({
    required this.storage,
  });

  /// 1) 요청 보낼 때
  /// 요청이 보내질때마다
  /// 만약에 요청의 Headers에 accessToken이 true 이면
  /// 실제 토큰을 가져와서 기존 accessToken값을 삭제하고 {
  /// authorization : Beader $토큰} 값을 추가
  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    print('[REQ] [${options.method}] ${options.uri}');

    if (options.headers['accessToken'] == 'true') {
      options.headers.remove('accessToken');

      final accessToken = await storage.read(key: ACCESS_TOKEN_KEY);
      options.headers.addAll(
        {'authorization': 'Bearer $accessToken'},
      );
    }

    if (options.headers['refreshToken'] == 'true') {
      options.headers.remove('refreshToken');

      final refreshToken = await storage.read(key: REFRESH_TOKEN_KEY);
      options.headers.addAll(
        {'authorization': 'Bearer $refreshToken'},
      );
    }

    return super.onRequest(options, handler);
  }

  /// 2) 응답 받을 때
  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print(
        '[RES] [${response.requestOptions.method}] ${response.requestOptions.uri}');

    super.onResponse(response, handler);
  }

  /// 3) 에러 났을 때
  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    // 401에러 났을 때 (status code)
    // 토큰을 재발급 받는 시도를하고 토큰이 재발급되면
    // 다시 새로운 토큰을 요청한다.

    print('[ERROR] [${err.requestOptions.method}] ${err.requestOptions.uri}');
    print('[ERROR] [MESSAGE] ${err.message}');

    // refreshToken이 없는 경우
    final refreshToken = await storage.read(key: REFRESH_TOKEN_KEY);
    if (refreshToken == null) {
      // 에러 던질때는 handler.reject()사용

      return handler.reject(err);
    }

    final isStatus401 = err.response?.statusCode == 401;
    final isPathRefresh = err.requestOptions.path == '/auth/token';

    if (isStatus401 && !isPathRefresh) {
      final dio = Dio();

      try {
        final res = await dio.post(
          'http://$ip/auth/token',
          options: Options(
            headers: {
              'authorization': 'Bearer $refreshToken',
            },
          ),
        );

        final accessToken = res.data['accessToken'];

        final options = err.requestOptions;

        //기존 토큰 변경
        options.headers.addAll({
          'authorization': 'Bearer $accessToken',
        });

        await storage.write(key: ACCESS_TOKEN_KEY, value: accessToken);

        // 에러난 요청options에 토큰값을 신규로 변경하여 재전송
        final resending = await dio.fetch(options);

        return handler.resolve(resending);
      } on DioException catch (e) {
        return handler.reject(e);
      }
    }

    return handler.reject(err);
  }
}
image.pngimage.png

 

답변 3

0

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

안녕하세요!

남겨주신 깃허브 살펴봤습니다.

너무 아쉬운 에러인데 바로 들으시겠습니까? 아니면 직접 한번 더 찾아보시겠습니까?

감사합니다!

dev님의 프로필 이미지
dev
질문자

어제는 제가 늪에 빠진거같아요.. 항상 개발하면서 느끼는거지만 에러가 발생 했을 때 침착하게 돌면 찾을수 있는거를 급해지면서 참,,,댓글 보고 한번 더 순차적으로 돌았는데 발견했습니다 감사합니다.

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

찾으셨다니 다행입니다!

0

dev님의 프로필 이미지
dev
질문자

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. dev님!

401 에러는 보통 인증에 관련된 문제입니다. 현재 여러분의 상황을 요약하자면, 토큰이 만료되어 재발급을 받고 다시 시도하는 중에도 여전히 401 오류가 발생하는 것입니다. 이 문제의 원인은 여러 가지가 있을 수 있습니다. 아래에서 몇 가지 가능한 해결책을 제시합니다.

  1. 토큰 저장과 로딩 확인: 새로운 accessToken을 제대로 저장하고 있는지 확인하세요. storage.write(key: ACCESS_TOKEN_KEY, value: accessToken); 이 부분이 성공적으로 작동하는지 디버깅으로 로그를 추가하여 확인해보세요. 또한, 토큰이 저장될 때나 읽어들일 때 올바른 키를 사용하고 있는지 확인하세요.

  2. 재시도 로직 확인: 에러가 발생했을 때 재시도하는 로직이 올바르게 작동하는지 확인합니다. options.headers.addAll({'authorization': 'Bearer $accessToken'}); 이 부분에서 새로운 토큰을 제대로 추가했는지 확인하고, dio.fetch(options); 를 통해 위의 options 객체가 정확하게 설정되어 있는지 확인해야 합니다.

  3. 서버 응답 확인: 서버 측에서 실제로 반환하는 에러 메시지를 살펴보세요. 401 에러가 발생하면 서버 로그에서도 해당 요청에 대해 무엇이 잘못되었는지 명확하게 보여줄 수 있습니다. 이는 서버 코드에서 발행되는 특정 상호 작용이나 잘못 설정된 토큰 정보일 수 있습니다.

  4. 디버그 로그 추가: 응답을 다시 시도하기 전과 후에 로그를 남겨, 어떤 과정에서 무엇이 잘못되는지 보다 명백하게 확인할 수 있습니다. 추가적인 디버깅 로그를 통해 문제가 발생하는 단계나 코드를 명확히 알 수 있습니다.

다음 링크도 참고해보시면 도움이 될 수 있습니다:

이외에도 상당수의 강의 참여자들이 유사한 문제를 겪고 해결한 사례를 볼 수 있으니, 비슷한 상황을 검색하시거나 관련 포스팅을 확인해보시는 것도 좋습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

dev님의 프로필 이미지

작성한 질문수

질문하기