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的存储和携带:
- 在
pubspec.yaml中添加依赖:
dependencies: cookie_jar: ^4.0.1 http: ^1.1.0
- 初始化带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
相关产品推荐
相关产品推荐

