JWT在Postman验证正常但Flutter移动端验证失败问题排查
Node.js JWT在Flutter移动端验证失败问题
使用Node.js后端生成JWT令牌对Flutter移动端用户进行身份验证时遇到问题:Postman中测试时令牌的生成与验证均正常,但移动端始终判定令牌无效。
后端(Node.js)代码
登录与令牌生成逻辑
const loginUser = async (req, res) => { const { username, password } = req.body; try { const user = await User.findOne({ username }) .populate('address', 'street reference city state postalCode coordinates isDefault'); if (!user) { return res.status(400).json({ message: 'Usuário não corresponde' }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(400).json({ message: 'Senha inválida' }); } const token = generateToken(user); // 将Mongoose文档转为JS对象 const userObj = user.toObject(); delete userObj.password; // 移除密码字段 res.json({ data: userObj, token }); } catch (err) { console.error(err.message); res.status(500).send('Erro no servidor'); } }; const generateToken = (user) => { return jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN }); }; const verifyToken = (token) => { return jwt.verify(token, process.env.JWT_SECRET); };
身份验证中间件(authMiddleware.js)
const authMiddleware = async (req, res, next) => { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ message: 'Acesso negado. Nenhum token fornecido.' }); } try { const decoded = jwtUtil.verifyToken(token, process.env.JWT_SECRET); req.user = await User.findById(decoded.id).select('-password'); if (!req.user) { return res.status(404).json({ message: 'Usuário não encontrado' }); } next(); } catch (err) { console.error('Algo deu errado com a verificação de token:', err.message); res.status(401).json({ message: 'Token não é válido' }); } }; const verifyAndAuthorization = (req, res, next) => { authMiddleware(req, res, () => { if (req.user.role === "Admin" || req.user.role === "Manager" || req.user.role === "Vendor" || req.user.role === "Customer" || req.user.role === "Delivery") { next() } else { res.status(403).json({ msg: "Você não tem autorização para acessar esta rota." }) } }) }
移动端(Flutter/Dart)代码
Future<void> sendLogin() async { try { if (formKeyLorgin.currentState?.saveAndValidate() ?? false) { final username = formKeyLorgin.currentState?.fields['username']?.value; final password = formKeyLorgin.currentState?.fields['password']?.value; final response = await _authRepository.login(username, password); userData.value = UserModel.fromJson(response); String user = jsonEncode(response['data']); String token = jsonEncode(response['token']); log(user); _storageService ..box.write('user', user) ..saveLogged(isLogged.value) ..saveToken(token); log(token); Get.offAndToNamed('/home'); } else {} } catch (e) { SnackbarService.showErrorMessage('$e'); log("Erro: $e", error: e); } }
已执行的排查步骤
- 令牌结构:确认Postman与移动端的令牌结构及载荷完全一致。
- 时间同步:确保服务器与设备时钟同步,排除令牌过期问题。
- HTTP请求头:检查Postman与移动端的HTTP请求头,确认Authorization头设置正确。
- 网络请求:通过开发者工具检查网络请求,未发现差异或错误。
- 令牌存储:尝试在设备上用不同方式存储令牌(如安全存储、Shared Preferences),排除存储相关问题。
附加信息
- 设备与系统:[请指定设备及操作系统]
- 移动端框架:Flutter/Dart
- JWT库:jsonwebtoken
- 网络环境:本地环境(含NGROK与不含NGROK两种情况)
- 数据库:MongoDB Atlas
问题排查与解决建议
移除令牌的JSON编码处理
移动端代码中使用jsonEncode(response['token'])会给JWT令牌添加多余的双引号,导致后端验证失败。修改为直接存储原始令牌:String token = response['token']; // 去掉jsonEncode调用可以打印存储前的令牌内容,确认没有多余的引号或转义字符。
修正后端验证函数的调用参数
后端verifyToken函数仅接收一个token参数,但authMiddleware中调用时传入了两个参数(token和process.env.JWT_SECRET)。虽然当前逻辑不会直接报错,但为了避免混淆,建议统一参数:- 要么修改
verifyToken函数支持传入密钥:const verifyToken = (token, secret) => { return jwt.verify(token, secret || process.env.JWT_SECRET); }; - 要么在
authMiddleware中直接调用jwt.verify,或者去掉多余的参数:const decoded = jwtUtil.verifyToken(token);
- 要么修改
验证请求头的令牌格式
在移动端发送请求时,确保Authorization头严格遵循Bearer <令牌>格式,令牌前后无多余空格。可以在请求拦截器中打印完整的请求头内容,确认令牌与Postman中使用的完全一致。
内容的提问来源于stack exchange,提问作者João Mello
相关产品推荐
相关产品推荐

