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

Express/TypeScript解析JSON请求体时如何白名单过滤User类属性

解决方案

你不需要手动逐字段赋值,Express + TypeScript 生态有成熟的标准化方案解决请求体字段白名单过滤问题,核心是在运行时做字段校验和裁剪——注意TS的类型注解仅在编译阶段生效,let user: User = request.body这种写法没有任何运行时防护,是安全隐患的根源。

常用的落地方式有三种,按项目场景选择即可:

方案1:类校验+类转换(TS面向对象风格项目首选)

这是TS后端领域通用性最高的标准方案,用class-validator做字段规则校验,class-transformer做普通JSON对象到类实例的转换,转换过程会自动过滤未在白名单内、或标记为排除的字段。

使用步骤:

  1. 改造User类,给允许用户提交的字段加校验规则,给敏感禁止提交的字段加排除标记:
import { IsString, IsNumber, Exclude } from 'class-validator';
import { Expose } from 'class-transformer';

export class User {
    @IsString()
    @Expose()
    public Username: string;

    @IsString()
    @Expose()
    public Password: string;

    @IsString()
    @Expose()
    public Email: string;

    // 敏感字段标记为排除,转换时自动丢弃用户传入的值
    @Exclude()
    public EmailVerified: boolean;

    @IsString()
    @Expose()
    public Country: string;

    @IsString()
    @Expose()
    public State: string;

    @IsString()
    @Expose()
    public City: string;

    @IsString()
    @Expose()
    public Gender: string;

    @IsNumber()
    @Expose()
    public BirthdayTicks: number;
}
  1. 在接口逻辑里做转换和校验,多余字段、敏感字段会被自动过滤,类型不符合的请求直接返回400错误:
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';

app.post('/users', express.json(), async (request, response, next) => {
    try {
        console.log(`${request.method} ${request.url} was called.`);
        // 把请求体转换为真正的User类实例,自动过滤非白名单字段
        const user = plainToInstance(User, request.body, {
            excludeExtraneousValues: true // 关键配置:自动剔除类里未标记@Expose的字段
        });
        // 校验字段格式
        const validationErrors = await validate(user);
        if (validationErrors.length > 0) {
            return response.status(400).send(validationErrors);
        }

        const sessionId: string = request.query.sessionId as string;
        const captcha: string = request.query.captcha as string;
        const createErrors: string[] = await UserStore.CreateUser(elastic, smtp, user, sessionId, captcha);
        response.status(createErrors.length <= 0 ? 201 : 400).send(createErrors);
    }
    catch (error) {
        next(error);
    }
});

方案2:白名单字段拾取(轻量小项目首选)

如果不想引入额外的装饰器依赖,直接定义允许用户传入的字段列表,从请求体里把对应字段摘出来即可,逻辑完全可控,几行代码就能实现:

// 定义允许用户提交的字段白名单
const ALLOWED_USER_FIELDS = [
    'Username', 'Password', 'Email',
    'Country', 'State', 'City',
    'Gender', 'BirthdayTicks'
] as const;

app.post('/users', express.json(), async (request, response, next) => {
    try {
        console.log(`${request.method} ${request.url} was called.`);
        // 仅从请求体中拾取白名单内的字段,其余字段全部丢弃
        const user = ALLOWED_USER_FIELDS.reduce((res, field) => {
            if (request.body[field] !== undefined) {
                res[field] = request.body[field];
            }
            return res;
        }, {} as Pick<User, typeof ALLOWED_USER_FIELDS[number]>);

        // 可补充基础类型校验逻辑,比如判断BirthdayTicks是否为数字
        const sessionId: string = request.query.sessionId as string;
        const captcha: string = request.query.captcha as string;
        const errors: string[] = await UserStore.CreateUser(elastic, smtp, user, sessionId, captcha);
        response.status(errors.length <= 0 ? 201 : 400).send(errors);
    }
    catch (error) {
        next(error);
    }
});

如果项目里已经引入了通用工具库,直接用内置的对象拾取方法就能实现同样的效果,不用自己写遍历逻辑。

方案3:Schema式校验(函数式风格项目首选)

如果不想把校验逻辑和类绑定,可以用Zod这类Schema校验库,直接定义允许的字段结构和类型,解析时自动过滤多余字段,同时自动生成TS类型,不用重复写类型定义:

import { z } from 'zod';

// 定义用户提交的字段Schema,未声明的字段会被自动过滤
const UserCreateSchema = z.object({
    Username: z.string(),
    Password: z.string(),
    Email: z.string().email(),
    Country: z.string(),
    State: z.string(),
    City: z.string(),
    Gender: z.string(),
    BirthdayTicks: z.number()
});

app.post('/users', express.json(), async (request, response, next) => {
    try {
        console.log(`${request.method} ${request.url} was called.`);
        // 解析校验请求体,多余字段自动剔除,类型错误直接返回失败
        const parseResult = UserCreateSchema.safeParse(request.body);
        if (!parseResult.success) {
            return response.status(400).send(parseResult.error.issues);
        }
        const user = parseResult.data;

        const sessionId: string = request.query.sessionId as string;
        const captcha: string = request.query.captcha as string;
        const errors: string[] = await UserStore.CreateUser(elastic, smtp, user, sessionId, captcha);
        response.status(errors.length <= 0 ? 201 : 400).send(errors);
    }
    catch (error) {
        next(error);
    }
});

注意事项

  • 所有方案的核心都是在数据传入数据库/ES之前做运行时的字段裁剪,绝对不要信任客户端传入的任何数据
  • 类似EmailVerified这种代表用户权限、状态的字段,永远不要从客户端请求体里取值,应该在服务端逻辑里默认赋值(比如新注册用户默认EmailVerified = false)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:39:20