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

Node.js实现仅商品创建者可更新/删除商品的权限控制

商品操作权限控制实现方案

核心问题排查

你现有代码存在3个直接导致权限逻辑无法实现的问题:

  • 商品模型的seller字段设置了unique: true约束,且类型为普通字符串,会导致一个用户只能创建1件商品,也无法和用户ID做关联匹配
  • 没有JWT鉴权逻辑,接口无法识别当前发起请求的登录用户身份
  • 注册接口生成JWT时错误引用了模型对象的ID,没有存入新注册用户的真实ID,所有注册返回的token都是无效身份
  • 创建商品时没有绑定创建者ID,更新、删除操作前没有做身份比对

分步实现代码

1. 修正商品模型定义

把seller字段改为关联用户表的ObjectId类型,移除错误的unique约束:

const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
    name:{
        type : String,
        required : true
    },
    category:{
        type : String,
        required : true
    },
    description :{
        type : String,
        required : true
    },
    price: {
        type : Number,
        required : true
    },
    // 修正seller字段:存储商品创建者的用户ID,关联用户模型
    seller: {
        type : mongoose.Schema.Types.ObjectId,
        ref: 'Register', // 保持和你用户模型导出的名称一致
        required : true
    },
    image: {
        type : String,
        required : true
    },
    Date:{
        type: Date,
        default: Date.now
    }
})
module.exports = mongoose.model('Product', productSchema);

2. 编写JWT鉴权中间件

新建鉴权中间件文件,所有需要登录的接口先经过该中间件解析当前用户身份:

const jwt = require('jsonwebtoken');
// 注意和你之前JWT签名用的密钥保持一致,如果你要读环境变量就去掉引号,直接写process.env.ACCESS_TOKEN
const JWT_SECRET = "process.env.ACCESS_TOKEN";

const authMiddleware = (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({error: "未登录,无访问权限"});
    }
    const token = authHeader.split(' ')[1];
    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        // 将解析出的当前登录用户ID挂载到req对象,后续路由可直接读取
        req.userId = decoded._id;
        next();
    } catch (error) {
        return res.status(401).json({error: "token无效或已过期"});
    }
}

module.exports = authMiddleware;

3. 修复注册接口的token生成bug

你原有注册接口生成token时误用了模型对象的ID,替换为新创建用户的真实ID:

// 找到signup路由里生成accessToken的代码段,替换为以下内容
const accessToken = jwt.sign(
    { _id: register._id }, // 原来写的Register._id是模型对象,要改成新创建的用户实例register的_id
    "process.env.ACCESS_TOKEN"
);

4. 改造商品路由接口

商品列表接口保持公开可访问,创建、更新、删除接口增加鉴权和权限校验逻辑:

const express = require('express');
const router = express.Router();
const Product = require('./models/Product'); // 替换为你本地的商品模型路径
const authMiddleware = require('./auth'); // 替换为你本地的鉴权中间件路径

// 公开接口:获取所有商品列表,登录/未登录用户都可访问
router.get("/products", async (req, res) => {
    try {
        // populate可联表查出商品创建者的用户名、邮箱,不需要可以删掉
        const products = await Product.find().populate('seller', 'username email');
        return res.status(200).json({ products });
    } catch (error) {
        console.log(error);
        return res.status(500).json({error: "服务器错误"});
    }
});

// 需登录接口:创建商品,自动绑定当前登录用户为商品创建者
router.post("/products", authMiddleware, async (req, res) => {
    try {
        const { name, category, description, price, image } = req.body;
        const newProduct = new Product({
            name,
            category,
            description,
            price,
            image,
            seller: req.userId // 直接取中间件解析出的当前用户ID作为创建者
        });
        await newProduct.save();
        return res.status(201).json({ product: newProduct });
    } catch (error) {
        console.log(error);
        return res.status(500).json({error: "商品创建失败"});
    }
});

// 需权限接口:更新商品,仅创建者可操作
router.put("/products/:id", authMiddleware, async (req, res) => {
    try {
        const productId = req.params.id;
        const product = await Product.findById(productId);
        if (!product) {
            return res.status(404).json({error: "商品不存在"});
        }
        // 比对商品创建者ID和当前登录用户ID,不一致直接返回403
        if (product.seller.toString() !== req.userId) {
            return res.status(403).json({error: "无权限操作,仅商品创建者可更新"});
        }
        // 权限校验通过,执行更新
        const updatedProduct = await Product.findByIdAndUpdate(
            productId,
            req.body,
            { new: true, runValidators: true }
        );
        return res.status(200).json({ product: updatedProduct });
    } catch (error) {
        console.log(error);
        return res.status(500).json({error: "商品更新失败"});
    }
});

// 需权限接口:删除商品,仅创建者可操作
router.delete("/products/:id", authMiddleware, async (req, res) => {
    try {
        const productId = req.params.id;
        const product = await Product.findById(productId);
        if (!product) {
            return res.status(404).json({error: "商品不存在"});
        }
        // 权限校验
        if (product.seller.toString() !== req.userId) {
            return res.status(403).json({error: "无权限操作,仅商品创建者可删除"});
        }
        // 校验通过执行删除
        await Product.findByIdAndDelete(productId);
        return res.status(200).json({message: "商品删除成功"});
    } catch (error) {
        console.log(error);
        return res.status(500).json({error: "商品删除失败"});
    }
});

module.exports = router;

注意事项

  • 前端请求创建/更新/删除商品接口时,必须在请求头携带Authorization: Bearer 登录返回的accessToken,否则会被鉴权中间件拦截
  • 因为seller字段存储的是ObjectId类型,和字符串类型的req.userId比对时必须先转成字符串,否则会因类型不一致一直判定无权限
  • 如果你之前已经创建过测试商品数据,因为旧数据的seller字段是字符串类型,需要先清空旧商品数据再测试新逻辑,否则会出现类型匹配错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 19:01:46