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

Flutter如何存储Cookie并实现用户登录状态鉴权

问题根因

Dart 官方http包默认不会自动持久化、自动携带服务端通过Set-Cookie响应头下发的Cookie。Postman测试正常是因为Postman内置了Cookie自动管理机制,你当前的Flutter登录逻辑只解析了响应体内容,完全没有读取响应头中的Cookie字段,自然无法在本地保存Token,后续请求也不会携带对应鉴权信息。

具体实现步骤

1. 引入必要依赖

放弃用默认的单次http客户端,改用支持Cookie自动管理的请求组件,在pubspec.yaml的dependencies节点添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.4.0
  dio_cookie_manager: ^3.1.1
  path_provider: ^2.1.2
  shared_preferences: ^2.2.2

执行flutter pub get完成安装。

2. 服务端跨域配置调整

因为APP请求属于跨域场景,要让Cookie能正常在客户端和服务端之间传递,需要给Express服务端添加跨域配置,先执行npm i cors安装cors中间件,在Express实例初始化的位置添加以下配置:

const cors = require('cors');
app.use(cors({
  origin: true, // 生产环境替换为正式请求源,调试阶段设为true可动态匹配
  credentials: true // 允许跨域请求携带Cookie
}))

同时补全你之前空实现的/authenticate路由逻辑,Token有效时返回200状态码,鉴权失败返回401:

router.get("/authenticate" ,authenticateToken, (req,res)=>{
  res.status(200).json({success: true, msg: "有效登录状态"})
})

3. 全局配置支持Cookie持久化的网络客户端

不要每次发请求都新建客户端实例,否则Cookie上下文会丢失,新建全局网络工具类统一管理请求:

import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class HttpUtil {
  static late Dio dio;
  static Future<void> init() async {
    dio = Dio(BaseOptions(
      baseUrl: 'http://192.168.8.23:3000',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      validateStatus: (status) => status! < 500,
    ));
    // 配置Cookie本地持久化存储路径
    Directory appDocDir = await getApplicationDocumentsDirectory();
    String cookieSavePath = '${appDocDir.path}/app_cookies/';
    // 添加Cookie管理拦截器,自动处理Cookie的存储、携带、过期清理
    dio.interceptors.add(CookieManager(PersistCookieJar(
      storage: FileStorage(cookieSavePath),
      ignoreExpires: false,
    )));
  }
}

在APP入口main.dart的main函数中先完成网络客户端初始化,再启动APP:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await HttpUtil.init();
  runApp(const MyApp());
}

4. 改造登录逻辑

用全局dio实例发登录请求即可,不需要手动解析、存储Cookie,Cookie拦截器会自动识别响应头中的Set-Cookie字段,把access-token持久化到本地,后续所有通过该dio实例发送的请求,都会自动携带有效Cookie,不需要手动加请求头。
修正后的登录方法:

// 注意:_absorbme要定义在State类顶层,不要写在setState内部,否则是局部变量不生效
bool _absorbme = false;

Future<void> _signIn(email, password) async {
  try {
    setState(() {
      LoadingWidget.show(context);
      _absorbme = true;
    });

    final response = await HttpUtil.dio.post(
      "/route/login",
      data: <String, String>{
        'email': email.toLowerCase(),
        'password': password,
      },
    );

    SharedPreferences prefs = await SharedPreferences.getInstance();
    final parse = response.data;
    await prefs.setString('msg', parse["msg"]);
    await prefs.setBool('loginSuccess', parse["success"] == 'true');

    if (parse["success"] == 'true') {
      if (mounted) Navigator.pushReplacementNamed(context, '/home');
    } else {
      if (mounted) ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(parse["msg"]))
      );
    }

  } catch (e) {
    if (mounted) ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("请求失败,请检查网络连接"))
    );
  } finally {
    if (mounted) {
      setState(() {
        LoadingWidget.hide(context);
        _absorbme = false;
      });
    }
  }
}

5. 实现登录状态鉴权流程

添加启动splash页,APP启动时先请求/route/authenticate接口校验本地Cookie是否有效,根据返回结果跳转对应页面:

  • 接口返回200:Cookie内Token有效,直接跳转主页
  • 接口返回401/403/请求失败:Token过期或未登录,跳转登录页
    示例启动页代码:
class SplashPage extends StatefulWidget {
  const SplashPage({super.key});

  @override
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  @override
  void initState() {
    super.initState();
    _checkAuthStatus();
  }

  Future<void> _checkAuthStatus() async {
    try {
      final res = await HttpUtil.dio.get("/route/authenticate");
      if (res.statusCode == 200 && mounted) {
        Navigator.pushReplacementNamed(context, '/home');
        return;
      }
      if (mounted) Navigator.pushReplacementNamed(context, '/login');
    } catch (e) {
      if (mounted) Navigator.pushReplacementNamed(context, '/login');
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

6. 登出逻辑实现

直接调用服务端的登出接口即可,服务端会清除Cookie,客户端的Cookie管理器会自动同步清理失效的Cookie,登出成功后跳转登录页:

Future<void> _handleLogout() async {
  try {
    await HttpUtil.dio.get("/route/logout");
    if (mounted) Navigator.pushReplacementNamed(context, '/login');
  } catch (e) {
    if (mounted) ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("登出失败,请重试"))
    );
  }
}
补充说明
  • 如果你坚持使用官方http包,需要手动从response.headers['set-cookie']字段解析Cookie字符串,通过SharedPreferences存储,之后每次发请求手动在headers中添加'cookie': 存储的Cookie字符串,但这种方式需要手动处理Cookie过期、多Cookie拼接、域名匹配等逻辑,出错概率高,不推荐。
  • Android模拟器访问本地电脑的Node服务时,若用127.0.0.1会指向模拟器自身,需要替换为10.0.2.2,你当前使用的局域网IP只要手机和电脑在同一网段可正常访问。
  • 不要在Flutter端尝试用Web端逻辑读取httpOnly Cookie,Flutter是原生请求栈,直接通过响应头读取Set-Cookie是合法操作,不存在浏览器端的跨域读取限制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:03:29