You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Token过期/未授权时跳转登录页的实现问题

Flutter Token过期/未授权自动跳转登录页问题

问题说明

  • 需求:用户Token过期或未授权时,应用自动跳转至登录页
  • 当前异常:Token过期/未授权时,应用抛出Exception错误,未执行跳转逻辑
  • 错误截图:
    错误截图

现有代码

登录接口代码

Future<User?> login(String nim, String password) async {
    String url = Constant.baseURL;
    try {
      var body = {
        'username': nim,
        'password': password,
      };
      var response = await http.post(
        Uri.parse(
          '$url/login_mhs',
        ),
        body: body,
      );
      if (response.statusCode == 200) {
        final token = jsonDecode(response.body)['data']['access_token'];
        await UtilSharedPreferences.setToken(token);
        print(token);
        print(await UtilSharedPreferences.getToken());
        return User.fromJson(jsonDecode(response.body));
      } else {
        return null;
      }
    } catch (e) {
      print(e);
      throw Exception();
    }
  }

获取数据接口代码

Future<UserBiodata> getDataMahasiswa() async {
    String url = Constant.baseURL;
    String token = await UtilSharedPreferences.getToken();
    final response = await http.get(
      Uri.parse(
        '$url/auth/mhs_siakad/biodata',
      ),
      headers: {
        'Authorization': 'Bearer $token',
      },
    );
    if (response.statusCode == 200) {
      return UserBiodata.fromJson(jsonDecode(response.body));
    } else {
      throw Exception();
    }
  }

Widget调用代码

TextButton(
  onPressed: () async {
    final prefs = await SharedPreferences.getInstance();
    prefs.setString(Constant.token, '');
    if (nimController.text.isEmpty || passwordController.text.isEmpty) {
      showError('NIM tidak sesuai');
    } else {
      setState(() {
        isLoading = true;
      });
      User? user = await Provider.of<Services>(context, listen: false)
          .login(nimController.text, passwordController.text);
      setState(() {
        isLoading = false;
      });
      if (user == null) {
        showError('NIM/Password tidak sesuai');
      } else {
        userProvider.user = user;
        Navigator.pushNamedAndRemoveUntil(
          context,
          '/main',
          (route) => false,
        );
      }
    }
  },
  style: TextButton.styleFrom(
    backgroundColor: primaryColor,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(66),
    ),
  ),
  child: Text(
    "Login",
    style: boldButton,
  ),
),

解决方案

1. 修改数据接口,处理Token失效状态码

在getDataMahasiswa接口中,针对401(未授权)、403(禁止访问)这类Token相关错误码,添加跳转登录页的逻辑:

Future<UserBiodata> getDataMahasiswa() async {
    String url = Constant.baseURL;
    String token = await UtilSharedPreferences.getToken();
    final response = await http.get(
      Uri.parse('$url/auth/mhs_siakad/biodata'),
      headers: {'Authorization': 'Bearer $token'},
    );
    if (response.statusCode == 200) {
      return UserBiodata.fromJson(jsonDecode(response.body));
    } else if (response.statusCode == 401 || response.statusCode == 403) {
      // 清空本地失效Token
      await UtilSharedPreferences.setToken('');
      // 跳转登录页并清空路由栈
      Navigator.pushNamedAndRemoveUntil(
        navigatorKey.currentContext!,
        '/login',
        (route) => false,
      );
      throw Exception('Token已过期,请重新登录');
    } else {
      throw Exception('获取数据失败');
    }
  }

2. 配置全局导航键

由于接口中可能无法直接获取Widget的context,需要在全局配置导航键:

// 全局定义导航键
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void main() {
  runApp(MyApp(navigatorKey: navigatorKey));
}

class MyApp extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey;
  const MyApp({super.key, required this.navigatorKey});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,
      routes: {
        '/login': (context) => LoginPage(),
        '/main': (context) => MainPage(),
      },
      initialRoute: '/login',
    );
  }
}

3. 调用接口时捕获异常

在Widget中调用接口时,添加try-catch避免未处理的异常崩溃:

// 示例调用getDataMahasiswa的代码
void fetchBiodata() async {
  try {
    UserBiodata biodata = await Provider.of<Services>(context, listen: false).getDataMahasiswa();
    // 处理获取到的用户数据
  } catch (e) {
    print(e.toString());
    // 可根据需求添加用户提示
  }
}

4. 全局异常捕获(可选)

如果希望统一处理所有HTTP请求的Token失效问题,可以封装HTTP请求工具类,在拦截器中统一处理状态码,避免每个接口重复写跳转逻辑。

内容的提问来源于stack exchange,提问作者sharon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 12:15:31