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

Flutter登录后调用getSpecialization接口遇403错误求助

问题描述

我用Flutter开发应用,当前处于登录流程的获取数据阶段,需要将数据存入缓存供后续使用。技术栈为前端Flutter、后端Node.js+MongoDB,需求是登录时获取specialization列表。

用户登录成功后,后端生成token并将userId、email和token存入Cookie,verifyAccessToken中间件会检查Cookie中的token,验证通过后getSpecialization接口返回专业列表。但在Flutter的登录API服务中调用getSpecializations时,verifyAccessToken中间件验证失败,返回403错误,无法获取列表,求解决。


后端代码

Node.js 登录接口

const authService = require("../../../services/authService");

exports.login = async (req, res, next) => {
    const { email, password } = req.body;
    console.log("login req body is ", req.body);
    //simple checker for username and password
    if (!email || !password) {
        return res.status(400).json({ message: "email or password is missing" });
    }

    try {
        //get the data from our auth service
        const { user, validPassword, token,  maxAge } = await authService.loginUser(email, password)
        //check if the user exists
        if (!user) {
            return res.status(401).json({
                message: "Login not successful",
                error: "User not found",
            });
        }
        //return error if the password is incorrect
        if (!validPassword) {
            return res.status(401).json({
                message: "Login not successful",
                error: "Password is incorrect",
            });
        }
        res.cookie("email", user.email, {
            httpOnly: true,
            maxAge: maxAge * 1000, // convert 2h to ms; maxAge uses milliseconds
          });

          res.cookie("userId", user._id,{
            httpOnly: true,
            maxAge: maxAge * 1000, // convert 2h to ms; maxAge uses milliseconds
          });
          
        //send our cookie with the token
        res.cookie("jwt", token, {
            httpOnly: true,
            maxAge: maxAge * 1000, //convert 2h to ms; maxAge uses miliseconds

        });

        //if everything is good return the user
        return res.status(200).json({
            message: "Login successful",
            user,
            
        });
       

    } catch (err) {
        res.status(401).json({
            message: "Login not successful",
            error: err.message,
        });
    }
};

verifyAccessToken 中间件

const jwt = require("jsonwebtoken");
require("dotenv").config();

//this only works if you use cookie-parser
const checkCookie = (req) => {
    console.log('inside checkCookie')
    console.log('all our cookies are: ', req.cookies)
    //we get the jwt cookie we need
    return req.cookies['jwt']
}

//middleware for verifying the JWT
const verifyAccessToken = (req, res, next) => {
    //define token
    let token;

    //authenticate through Bearer token
    if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) {
        token = req.headers.authorization.split(' ')[1]
        //logs so you see what's happening
        console.log('auth bearer token')
        console.log({ token })
    }
    else {
        token = req.headers["x-access-token"] || checkCookie(req) || null
        //logs
        console.log('our token is from x-access-token header, a cookie or null')
        console.log({ token })
    }

    //if we can't get our token anywhere then the response is an error
    if (!token) {
        return res.status(403).send("A token is required for authentication");
    }
    try {
        //we use the JWT library to verify the token
        //and we need to know the token_key which we encrypted our information
        const decoded = jwt.verify(token, process.env.TOKEN_KEY);
        //log
        console.log({ decoded })
        //the middleware adds the user information to the request and sends it to the route
        req.user = decoded;
    } catch (err) {
        return res.status(401).send("Invalid Token");
    }
    //if you have doubts check Express middleware docs: http://expressjs.com/en/guide/using-middleware.html
    return next();
};

module.exports = verifyAccessToken;

getSpecialization 接口

const {Specialization} = require('../../../models/specialization')


exports.getSpecialization = async (req, res) => {
    try {
      const specializzazioni = await Specialization.find({}, { _id: 0, __v: 0 }); // Escludi _id e __v dalla risposta
      res.json(specializzazioni);
    } catch (error) {
      console.error('Errore durante il recupero delle specializzazioni:', error);
      res.status(500).json({ message: 'Errore durante il recupero delle specializzazioni' });
    }
  };

路由配置

router.route("/login").post(login);


//router.use(verifyAccessToken);
router.use(verifyAccessToken);
router.route("/book").post(book);
router.route("/getbooking/:id").get(getBooking);
router.route("/logout").get(logout);
router.route("/getSpecialization").get(getSpecialization);

Flutter 前端代码

登录API服务

static Future<bool> login(LoginRequestModel model) async {
    Map<String, String> requestHeaders = {
      'Content-Type': 'application/json',
    };

    var url = Uri.http(Config.apiUrl, Config.loginAPI);

    var response = await client.post(url,
        headers: requestHeaders,
        body: jsonEncode(model.toJson())
    );

    if (response.statusCode == 200) {
      // Settare i dettagli del login
      await SharedService.setLoginDetails(loginResponseJson(response.body));

      // Ottenere il valore del cookie 'set-cookie' dalla risposta
      String? setCookie = response.headers['set-cookie'];

      if (setCookie != null) {
        // Estrarre il valore del token dal cookie
        String? token = parseCookieValue(setCookie);
        print (token);

        if (token != null) {
          SharedPreferences prefs = await SharedPreferences.getInstance();
          await prefs.setString('jwt', token);
        }
      }
      await getSpecializations();
      return true;
    } else {
      return false;
    }
  }

getSpecializations 方法

static Future<List<Specialization>> getSpecializations() async {
    var url = Uri.http(Config.apiUrl, Config.getSpecializaionAPI);

    var response = await client.get(url);

    if (response.statusCode == 200) {
      // Converte la risposta JSON in una lista di oggetti Specialization
      List<dynamic> jsonList = jsonDecode(response.body);
      List<Specialization> specializations = jsonList
          .map((item) => Specialization(
        specId: item['specId'],
        description: item['description'],
      ))
          .toList();

      return specializations;
    } else {
      return []; // o gestisci l'errore in base alle tue esigenze
    }
  }

问题原因与解决方案

原因

后端设置的jwt Cookie带有httpOnly: true属性,Flutter默认的http客户端不会自动管理和携带Cookie,导致调用getSpecializations时请求中没有包含验证所需的jwt Cookie,中间件找不到token返回403错误。

解决方案

方案1:使用Cookie管理库自动携带Cookie

安装cookie_jar库来自动处理Cookie的存储和携带:

  1. 在pubspec.yaml中添加依赖:
dependencies:
  cookie_jar: ^4.0.1
  http: ^1.1.0
  1. 初始化带Cookie管理的客户端:
import 'package:cookie_jar/cookie_jar.dart';
import 'package:http/io_client.dart';
import 'dart:io';

// 初始化持久化CookieJar(可选,默认内存存储)
final cookieJar = PersistentCookieJar(ignoreExpires: true);
final httpClient = HttpClient();
// 绑定CookieJar到HttpClient
httpClient.interceptors.add(CookieManager(cookieJar));
final client = IOClient(httpClient);

后续所有请求都会自动携带登录时获取的Cookie,无需手动处理。

方案2:手动在请求头中添加Cookie

从SharedPreferences取出保存的jwt,在getSpecializations中手动添加Cookie请求头:

static Future<List<Specialization>> getSpecializations() async {
    var url = Uri.http(Config.apiUrl, Config.getSpecializaionAPI);
    
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String? jwt = prefs.getString('jwt');
    
    // 构建请求头,携带jwt Cookie
    Map<String, String> headers = {};
    if (jwt != null) {
      headers['Cookie'] = 'jwt=$jwt';
    }

    var response = await client.get(url, headers: headers);

    if (response.statusCode == 200) {
      List<dynamic> jsonList = jsonDecode(response.body);
      List<Specialization> specializations = jsonList
          .map((item) => Specialization(
        specId: item['specId'],
        description: item['description'],
      ))
          .toList();
      return specializations;
    } else {
      return [];
    }
  }

额外注意:跨域配置

如果前后端存在跨域,后端需要配置CORS允许携带Cookie:

const cors = require('cors');
app.use(cors({
  origin: '你的Flutter应用地址', // 例如http://localhost:5000或移动端的域名
  credentials: true // 必须开启,允许携带Cookie
}));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:17:02