passportjs保护页面实现公开分享的最优方案及最小重构方法
Node.js Passport 项目公开页面功能实现方案
核心思路
在不改动现有业务逻辑的前提下,通过新增两类中间件实现权限解耦,同时扩展数据模型的字段和返回方法,兼顾当前需求和后续扩展性。
具体实现步骤
- 第一步:扩展现有数据模型,新增公开标识字段和公开数据过滤方法,默认值设为私有,完全兼容原有业务逻辑
- 第二步:新增可选认证中间件,复用现有Passport的认证逻辑,不强制要求用户登录,仅对有有效凭证的请求挂载用户信息
- 第三步:新增资源级权限校验中间件,统一处理公开/私有资源的访问规则,避免在每个路由重复编写权限逻辑
- 第四步:新增公开资源的访问路由,组合使用上述两个中间件,原有私有路由完全保留原有
ensureAuth逻辑无需改动
代码示例
1. 扩展Blend数据模型
const blendSchema = new mongoose.Schema({ // 原有所有字段保持不变 isPublic: { type: Boolean, default: false, // 默认私有,不影响已有页面的访问规则 required: true } }) // 新增实例方法:过滤返回给公开访问的非敏感字段 blendSchema.methods.getPublicFields = function() { // 自行调整要过滤的敏感字段,比如所属用户ID、未发布内容等 const { user, unpublishedContent, ...publicFields } = this.toObject() return publicFields } const Blend = mongoose.model('Blend', blendSchema)
2. 新增可选认证中间件
// 可选认证:不强制登录,有有效会话就挂载用户信息,没有也放行 const optionalAuth = (req, res, next) => { passport.authenticate('session', (err, user) => { if (err) return next(err) if (user) req.user = user next() })(req, res, next) }
3. 新增Blend资源权限校验中间件
/** * 校验Blend资源访问权限 * @param {'read' | 'write'} requiredPerm 所需权限 */ const checkBlendAccess = (requiredPerm = 'read') => async (req, res, next) => { const blend = await Blend.findById(req.params.id) if (!blend) return res.status(404).send('页面不存在') // 挂载资源和权限标识到请求对象,后续路由无需重复查询 req.blend = blend req.isOwner = req.user?.[0]?._id?.toString() === blend.user.toString() // 写权限(编辑、删除)必须要求是资源所有者 if (requiredPerm === 'write') { return req.isOwner ? next() : res.status(403).send('无操作权限') } // 读权限:公开页面 或 所有者访问 直接放行 if (blend.isPublic || req.isOwner) { return next() } // 私有页面非所有者访问返回403 return res.status(403).send('该页面为私有页面') }
4. 新增公开访问路由,原有路由无需改动
// 新增公开可读的页面路由 router.get('/:id', optionalAuth, checkBlendAccess('read'), async (req, res) => { const { blend, isOwner } = req // 所有者返回全量数据,公开访问返回过滤后的非敏感数据 const returnData = isOwner ? blend : blend.getPublicFields() res.send(returnData) }) // 原有路由完全保留,无需任何改动 router.post('/', ensureAuth, async (req, res) => { const newBlend = new Blend({ ...req.body, user: req.user[0]._id }) // 原有逻辑不变 try { await newBlend.save() res.send(newBlend) } catch (err) { if (err.name === 'ValidationError') { return res.status(400).send( Object.values(err.errors) .map((val) => val.message) .toString() ) } res.status(400).send(err.message) } })
后续扩展建议
如果后续需要支持更多类型资源的公开分享,可以把checkBlendAccess封装为通用的资源校验中间件,传入模型类型参数即可复用,无需重复编写权限逻辑。
内容的提问来源于stack exchange,提问作者Enes
相关产品推荐
相关产品推荐

