如何实现仅允许发布者编辑/删除MongoDB帖子(Express.js)
帖子权限控制实现方案(Express.js + MongoDB)
需求背景
我的应用基于MongoDB实现了帖子的增删改查(CRUD)功能,但存在权限漏洞:任意登录用户均可修改或删除任意帖子。需要实现仅允许发布该帖子的用户对其执行CRUD操作的权限控制,使用Express.js完成。
客户端UserProvider.js代码
import axios from "axios"; import UserContext from "./UserContext"; import React from "react"; export const UserProvider = (props) => { const baseUrl = "http://localhost:3000/api/users"; function createUser(username, password, firstName, lastName) { let user = { username, password, firstName, lastName }; // changes here return axios.post(baseUrl, user) .then(response => { return new Promise(resolve => resolve(response.data)); } ); } function signInUser(username, password) { let user = { username, password }; return axios.post(`${baseUrl}/login`, user) .then(response => { localStorage.setItem('myCoffeeToken', response.data.token) return new Promise(resolve => resolve(response.data)); } ); } return ( <UserContext.Provider value={{ createUser, signInUser }}> { props.children } </UserContext.Provider> ) }
权限控制实现步骤
1. 给帖子集合添加发布者关联字段
MongoDB的posts集合必须新增author字段,用来存储发布该帖子的用户_id(ObjectId类型)。示例帖子模型:
// models/Post.js const mongoose = require('mongoose'); const postSchema = new mongoose.Schema({ title: String, content: String, author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', // 关联用户模型 required: true }, // 其他业务字段... }); module.exports = mongoose.model('Post', postSchema);
2. 编写身份验证中间件
先实现解析JWT令牌的中间件,提取当前登录用户的ID并挂载到请求对象上:
// middleware/auth.js const jwt = require('jsonwebtoken'); const User = require('../models/User'); module.exports = async (req, res, next) => { // 从请求头提取令牌 const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ message: '未授权,请先登录' }); } try { // 验证并解码令牌 const decoded = jwt.verify(token, process.env.JWT_SECRET); // 查找用户并挂载到req对象(排除密码字段) req.user = await User.findById(decoded.userId).select('-password'); next(); } catch (err) { res.status(401).json({ message: '令牌无效' }); } };
3. 编写帖子权限校验中间件
创建中间件检查当前登录用户是否为帖子的发布者:
// middleware/postAuth.js const Post = require('../models/Post'); module.exports = async (req, res, next) => { try { // 根据URL参数获取目标帖子 const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ message: '帖子不存在' }); } // 校验用户ID与帖子作者ID是否匹配 if (post.author.toString() !== req.user._id.toString()) { return res.status(403).json({ message: '无权限操作此帖子' }); } // 校验通过,将帖子挂载到req对象供后续路由使用 req.post = post; next(); } catch (err) { res.status(500).json({ message: '服务器错误' }); } };
4. 改造帖子路由,添加权限控制
在帖子的CRUD路由中,结合上面两个中间件实现权限控制:
// routes/posts.js const express = require('express'); const router = express.Router(); const auth = require('../middleware/auth'); const postAuth = require('../middleware/postAuth'); const Post = require('../models/Post'); // 创建帖子 - 仅登录用户可操作,自动关联当前用户为作者 router.post('/', auth, async (req, res) => { try { const post = new Post({ ...req.body, author: req.user._id // 绑定当前登录用户ID }); await post.save(); res.status(201).json(post); } catch (err) { res.status(400).json({ message: err.message }); } }); // 获取单个帖子(公开访问,如需仅作者查看可添加postAuth中间件) router.get('/:id', async (req, res) => { try { const post = await Post.findById(req.params.id).populate('author', 'username firstName'); if (!post) { return res.status(404).json({ message: '帖子不存在' }); } res.json(post); } catch (err) { res.status(500).json({ message: err.message }); } }); // 修改帖子 - 需登录+权限校验 router.put('/:id', auth, postAuth, async (req, res) => { try { // 直接使用postAuth中间件挂载的req.post进行更新 Object.assign(req.post, req.body); await req.post.save(); res.json(req.post); } catch (err) { res.status(400).json({ message: err.message }); } }); // 删除帖子 - 需登录+权限校验 router.delete('/:id', auth, postAuth, async (req, res) => { try { await req.post.remove(); res.json({ message: '帖子已删除' }); } catch (err) { res.status(500).json({ message: err.message }); } }); module.exports = router;
5. 客户端请求时携带令牌
确保客户端在调用帖子接口时,将myCoffeeToken放入请求头的Authorization字段,可通过axios拦截器实现自动携带:
// 在UserProvider中添加带拦截器的axios实例 const api = axios.create({ baseURL: 'http://localhost:3000/api', }); // 请求拦截器:自动在请求头添加令牌 api.interceptors.request.use(config => { const token = localStorage.getItem('myCoffeeToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 后续使用api实例发起帖子相关请求即可
内容的提问来源于stack exchange,提问作者Aquiles Bailo
相关产品推荐
相关产品推荐

