• 카테고리

    질문 & 답변
  • 세부 분야

    모바일 앱 개발

  • 해결 여부

    미해결

안드로이드 에뮬레이터 403 에러

24.01.24 18:44 작성 24.01.24 18:47 수정 조회수 276

0

E/flutter ( 9967): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [bad response]: This exception was thrown because the response has a status code of 403 and RequestOptions.validateStatus was configured to throw for this status code.
E/flutter ( 9967): The status code of 403 has the following meaning: "Client error - the request contains bad syntax or cannot be fulfilled"
E/flutter ( 9967): Read more about status codes at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
E/flutter ( 9967): 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.
E/flutter ( 9967): 
E/flutter ( 9967): #0      DioMixin.fetch.<anonymous closure> (package:dio/src/dio_mixin.dart:507:7)
E/flutter ( 9967): #1      _FutureListener.handleError (dart:async/future_impl.dart:180:22)
E/flutter ( 9967): #2      Future._propagateToListeners.handleError (dart:async/future_impl.dart:858:47)
E/flutter ( 9967): #3      Future._propagateToListeners (dart:async/future_impl.dart:879:13)
E/flutter ( 9967): #4      Future._completeError (dart:async/future_impl.dart:655:5)
E/flutter ( 9967): #5      _SyncCompleter._completeError (dart:async/future_impl.dart:63:12)
E/flutter ( 9967): #6      _Completer.completeError (dart:async/future_impl.dart:27:5)
E/flutter ( 9967): #7      Future.any.onError (dart:async/future.dart:618:45)
E/flutter ( 9967): #8      _RootZone.runBinary (dart:async/zone.dart:1666:54)
E/flutter ( 9967): #9      _FutureListener.handleError (dart:async/future_impl.dart:177:22)
E/flutter ( 9967): #10     Future._propagateToListeners.handleError (dart:async/future_impl.dart:858:47)
E/flutter ( 9967): #11     Future._propagateToListeners (dart:async/future_impl.dart:879:13)
E/flutter ( 9967): #12     Future._completeError (dart:async/future_impl.dart:655:5)
E/flutter ( 9967): #13     Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:745:7)
E/flutter ( 9967): #14     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter ( 9967): #15     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

IOS 시뮬레이터로 잘만 해왔다가, 안드로이드 에뮬로도 잘 되는지 확인해보려고 했는데 로그인 할 때 403에러가 뜨네요... 어떻게 해결해야 할까요?

 

// localhost
const emulatorIP = '10.0.2.2:3000';
const simulatorIP = '127.0.0.1:3000';

final ip = Platform.isAndroid ? emulatorIP : simulatorIP;

답변 1

답변을 작성해보세요.

0

안녕하세요!

403에러는 네트워크 에러는 아닙니다.

403이라면 지금 코드 그대로 시뮬레이터에 실행했을때 똑같은 에러가 나야 할 것 같습니다 (같은 순간에는)

IP를 보여주셨는데 403은 통신이 이미 된 상태에서의 에러이기 때문에 IP를 잘못 입력한게 아닙니다.

에러 위치 다시한번 파악해서 질문 주시면 추가 도움 드리겠습니다.

감사합니다!

진표님의 프로필

진표

질문자

2024.01.25

onPressed: () async {
                      final dio = Dio();

                      final rawString = '$username:$password';
                      Codec<String, String> stringToBase64 = utf8.fuse(base64);
                      String token = stringToBase64.encode(rawString);

                      print("Before Post");

                      final resp = await dio.post("http://$ip/auth/login",
                          options: Options(
                            headers: {'authorization': 'Basic $token'},
                          ));

                      print("After Post");

                      final refreshToken = resp.data['refreshToken'];
                      final accessToken = resp.data['accessToken'];

                      final storage = ref.read(storageProvider);

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

                      Navigator.of(context).push(
                          MaterialPageRoute(builder: (_) => const RootTab()));
                    },

이게 로그인을 할 때 onPressed 함수인데요,

 

                      final resp = await dio.post("http://$ip/auth/login",
                          options: Options(
                            headers: {'authorization': 'Basic $token'},
                          ));

이 부분에서 에러가 발생하는 듯 합니다.

print("Before Post");의 로그는 출력되지만 print("After Post");의 로그가 출력되지 않아요.

 

정말 이상한건 IOS 시뮬레이터로는 로그인도 되고음식 사진들도 잘 뜨는데 왜 안드로이드 에뮬레이터는 로그인이 안되는건지 잘 모르겠습니다.. 에뮬레이터 새로운거 설치해서 해봤는데도 똑같네요.

 

음... 혹시 제가 IOS 시뮬레이터로 로그인을 해놓아서..? 그런걸까요? 예를 들면 누군가 계정을 사용중인데 다른 누군가가 동일한 계정으로 로그인 하려고 하면 기존 사용자가 쓰던 계정이 강제 로그아웃 되는 그런 예시 있잖아요? 약간 그런 느낌일까요? 제가 이거 로그아웃을 시키고 에뮬레이터로 해보고 싶은데 로그아웃 시키는 방법을 모르겠네요.

중복 로그인을 방지하고있지는 않습니다.

에러가 발생하는 위치는 요청을 보내는곳이 맞습니다.

403이 에러라는 의미니까요.

헤더에 토큰이 잘 인코딩 돼있는지 먼저 확인 해보세요.

iOS 시뮬레이터에서도 앱을 삭제 후 다시 실행한 후에도 실행 잘 되는지 확인 부탁드립니다.

감사합니다!

진표님의 프로필

진표

질문자

2024.01.27

import 'dart:convert';

import 'package:codefactory_lecture/common/const/colors.dart';
import 'package:codefactory_lecture/common/const/data.dart';
import 'package:codefactory_lecture/common/layout/default_layout.dart';
import 'package:codefactory_lecture/common/view/root_tab.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../common/component/custom_text_form_field.dart';

class LoginScreen extends ConsumerWidget {
  const LoginScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    String username = '';
    String password = '';

    return DefaultLayout(
      child: SingleChildScrollView(
        keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
        child: SafeArea(
          top: true,
          bottom: false,
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const _Title(),
                const SizedBox(
                  height: 16,
                ),
                const _SubTitle(),
                Image.asset(
                  'asset/img/misc/logo.png',
                  width: MediaQuery.of(context).size.width / 3 * 2,
                ),
                CustomTextFormField(
                  hintText: "이메일을 입력해주세요",
                  onChanged: (String value) {
                    username = value;
                    print("username: $username");
                  },
                ),
                const SizedBox(
                  height: 16,
                ),
                CustomTextFormField(
                  hintText: "이메일을 입력해주세요",
                  onChanged: (String value) {
                    password = value;
                    print("password: $password");
                  },
                ),
                const SizedBox(
                  height: 16,
                ),
                ElevatedButton(
                    onPressed: () async {
                      final dio = Dio();

                      print("onPressed username: $username");
                      print("onPressed password: $password");

                      final rawString = '$username:$password';
                      Codec<String, String> stringToBase64 = utf8.fuse(base64);
                      String token = stringToBase64.encode(rawString);

                      print("rawString: $rawString");
                      print("token: $token");

                      final resp = await dio.post("http://$ip/auth/login",
                          options: Options(
                            headers: {'authorization': 'Basic $token'},
                          ));

                      final refreshToken = resp.data['refreshToken'];
                      final accessToken = resp.data['accessToken'];

                      final storage = ref.read(storageProvider);

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

                      Navigator.of(context).push(
                          MaterialPageRoute(builder: (_) => const RootTab()));
                    },
                    style: ElevatedButton.styleFrom(
                        backgroundColor: PRIMARY_COLOR,
                        foregroundColor: Colors.white,
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(5))),
                    child: const Text("로그인")),
                TextButton(
                    onPressed: () async {},
                    style: TextButton.styleFrom(foregroundColor: Colors.black),
                    child: const Text("회원가입")),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _Title extends StatelessWidget {
  const _Title({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text(
      "환영합니다",
      style: TextStyle(
          fontSize: 34, fontWeight: FontWeight.w500, color: Colors.black),
    );
  }
}

class _SubTitle extends StatelessWidget {
  const _SubTitle({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text(
      "이메일과 비밀번호를 입력해서 로그인 해주세요!\n오늘도 성공적인 주문이 되길 :)",
      style: TextStyle(fontSize: 16, color: BODY_TEXT_COLOR),
    );
  }
}

코팩님!! 문제를 드디어 알아냈습니다.. IOS로 했을 때는 rawString 값이 제대로 담겼는데 안드로이드 에뮬레이터로 했을 때 onChanged함수를 통해서 value값을 저장하는데도 버튼을 눌러서 rawString값 및 username과 password를 프린트 함수로 찍어보면 값이 바뀌어 있지 않고 build함수 초기에 선언했던 username과 password 변수의 ' ' 공백 그대로 찍히더라구요. 그래서 안되던 거였어요..!

I/flutter (11949): onPressed username: 
I/flutter (11949): onPressed password: 
I/flutter (11949): rawString: :
I/flutter (11949): token: Og==

이게 안드로이드 에뮬레이터로 했을 때 로그가 저렇게 찍혀요.

flutter: onPressed username: test@codefactory.ai
flutter: onPressed password: testtest
flutter: rawString: test@codefactory.ai:testtest
flutter: token: dGVzdEBjb2RlZmFjdG9yeS5haTp0ZXN0dGVzdA==

IOS 시뮬레이터로 했을땐 이렇게 잘 저장이 되어서 로그인이 잘 됩니다.

 

진짜 너무 황당하네요... 심지어는 안드로이드 에뮬레이터로 했을 때, onChanged 함수에 print로 출력한 username과 password는 잘 찍히네요. 근데 버튼 눌렀을 때는 저렇게 값이 다 사라져버립니다. ConsumerStatefulWidget으로 바꿔봤는데도 안되고 리버팟을 빼고 그냥 일반 stateful, stateless 위젯으로도 바꿔봤는데도 안되네요. 구글링을 했는데도 뭐 딱히 건진게 없네요... 그냥 일단 IOS 시뮬레이터로 강의 쭉 들어도 상관 없겠지만... 이게 왜 안되는건지... ㅠㅠ

 

플러터는 크로스플랫폼 실행을 했을때 로직이 다르게 실행되도록 하는 기능은 존재하지 않기 때문에 무언가 잘못하고 계시는게 있을거라 생각됩니다. 간혹 안드로이드 에뮬레이터에서 옛날 버전의 앱을 실행하고 있는데 현재 버전의 앱을 실행하고 있는걸로 착각하는 분들이 계십니다. 안드로이드 에뮬레이터에서 현재 실행중인 앱을 "확실히" 종료 해주시고 다시 빌드해서 실행 해보세요. 아마 onPressed 함수가 제대로 작성되기 전의 버전이 실행되고 있었을 확률이 높습니다.