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
相关产品推荐
相关产品推荐

