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

TypeScript中JWT验证时req.user类型不匹配问题求助

解决TypeScript中JWT验证时req.user的类型不兼容问题

问题梳理

编写JWT验证中间件时遇到以下类型错误:

  • 直接赋值req.user = user时,提示类型‘undefined’不能赋值给类型‘object’(ts(2322))
  • 添加if(user !== undefined)判断后,又出现类型‘string’不能赋值给自定义用户对象类型(2322)
  • 尝试将req.user定义为any:导致所有JWT相关函数报错
  • 定义为object:提示No overload matches this call

核心矛盾:jwt.verify回调返回的user类型是string | jwt.JwtPayload | undefined,和自定义的req.user类型无法匹配。

原因分析

  1. Payload与自定义类型不匹配:签名时只存入了id和isAdmin,但全局扩展的req.user定义了User模型的所有字段,类型结构不一致。
  2. 类型守卫缺失:jwt.verify返回的decoded可能是字符串、对象或undefined,未做类型判断就直接赋值。
  3. 必填字段冲突:全局扩展的req.user被定义为必填,但验证过程中可能出现无效token导致user不存在的情况。

解决方案

根据实际需求选择以下两种方案:

方案1:仅使用JWT Payload中的信息

适合不需要完整用户数据的场景,只保留签名时存入的字段。

1.1 修改全局Request类型扩展

// @types/index.d.ts
declare global {
    namespace Express {
        interface Request {
            user?: {
                id: string;
                isAdmin: boolean;
            };
        }
    }
}
export {}; // 必须添加此语句,将文件标记为模块

1.2 完善验证中间件的类型处理

// jwtVerify.ts
import * as jwt from 'jsonwebtoken'
import { Request, Response, NextFunction } from 'express';

// 定义JWT Payload的类型
interface JwtPayload {
    id: string;
    isAdmin: boolean;
}

export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
    const sec = process.env.JWT_SEC as string;
    const authHeader = req.headers.token;

    if (authHeader) {
        const token = (authHeader as string).split(' ')[1];
        jwt.verify(token, sec, (err, decoded) => {
            if (err) {
                return res.status(403).json('Token is not valid');
            }
            // 类型守卫:排除string类型,确保是有效Payload对象
            if (typeof decoded !== 'string' && decoded) {
                req.user = decoded as JwtPayload;
                next();
            } else {
                res.status(403).json('Invalid token payload');
            }
        });
    } else {
        return res.status(401).json('You are not authorized');
    }
};

方案2:获取完整用户信息

需要完整用户数据时,在验证JWT后查询数据库,返回符合User模型的类型。

2.1 修改全局Request类型扩展

// @types/index.d.ts
import { UserDocument } from '../models/User'; // 导入User模型的类型

declare global {
    namespace Express {
        interface Request {
            user?: Omit<UserDocument, 'password'>; // 排除敏感的password字段
        }
    }
}
export {};

2.2 验证后查询数据库

// jwtVerify.ts
import * as jwt from 'jsonwebtoken'
import { Request, Response, NextFunction } from 'express';
import User from '../models/User'; // 导入User模型

export const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
    const sec = process.env.JWT_SEC as string;
    const authHeader = req.headers.token;

    if (authHeader) {
        const token = (authHeader as string).split(' ')[1];
        jwt.verify(token, sec, async (err, decoded) => {
            if (err) {
                return res.status(403).json('Token is not valid');
            }
            // 确保decoded是包含id的对象
            if (typeof decoded !== 'string' && decoded?.id) {
                // 查询数据库并排除password字段
                const user = await User.findById(decoded.id).select('-password');
                if (!user) {
                    return res.status(404).json('User not found');
                }
                // 将Mongoose文档转为普通对象并赋值
                req.user = user.toObject() as typeof req.user;
                next();
            } else {
                res.status(403).json('Invalid token payload');
            }
        });
    } else {
        return res.status(401).json('You are not authorized');
    }
};

注意事项

  • 确保@types/index.d.ts被TypeScript识别,可在tsconfig.json的include数组中添加"./@types/**/*"
  • 签名JWT时,避免存入敏感数据(如密码),只保留必要字段
  • 类型断言需谨慎,确保经过足够的类型守卫判断后再使用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:10:41