课程介绍
认证中间件位于传入请求与路由处理函数之间,在授予对受保护资源的访问权限之前,验证用户是否为其声称的身份。在本课程中,你将构建基于 JWT 的认证中间件,并将其应用于特定的路由,把开放的 API 转变为一个安全的、可用于生产环境的系统。
中间件函数可以访问请求对象(request object)、响应对象(response object),以及调用链中的下一个中间件函数。认证中间件会检查 Authorization 请求头,验证 token,将用户信息附加到请求上,然后决定将控制权传递给下一个处理函数,还是拒绝该请求。
构建认证中间件
创建一个中间件函数,用于提取并验证 JWT token。该中间件检查 Authorization 请求头中的 Bearer token,使用你的密钥对其进行验证,并将解码后的 payload 附加到请求对象上,供下游处理函数使用。
javascript
const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
module.exports = authenticate;
该中间件通过移除 Bearer 前缀来提取 token,使用你的密钥对其进行验证,并将解码后的用户数据存储在 req.user 中。如果验证失败,它会返回 401 状态码而不调用 next,从而终止请求处理流程。

使用中间件保护路由
将认证中间件应用于需要用户身份的路由。你可以通过在处理函数之前插入中间件来保护单个路由、成组路由,或整个路由器。
javascript
const express = require('express');
const authenticate = require('./middleware/authenticate');
const router = express.Router();
router.get('/profile', authenticate, (req, res) => {
res.json({
userId: req.user.id,
email: req.user.email,
message: 'This is your protected profile'
});
});
router.post('/posts', authenticate, async (req, res) => {
const post = await createPost({
...req.body,
authorId: req.user.id
});
res.status(201).json(post);
});
router.get('/public', (req, res) => {
res.json({ message: 'This endpoint is public' });
});
authenticate 中间件在路由处理函数之前运行。没有应用该中间件的路由仍然可以公开访问。由中间件填充的 req.user 对象使处理函数能够访问已认证用户的身份信息。
基于角色的访问控制
扩展你的中间件,通过检查用户角色或权限来处理授权。创建一个包装函数,接受所需角色作为参数,并返回强制执行这些要求的中间件。
javascript
const authorize = (...allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
router.delete('/users/:id', authenticate, authorize('admin'), async (req, res) => {
await deleteUser(req.params.id);
res.status(204).send();
});
router.put('/posts/:id', authenticate, authorize('admin', 'editor'), async (req, res) => {
const post = await updatePost(req.params.id, req.body);
res.json(post);
});
authorize 中间件假定 authenticate 已经运行并将 req.user 附加到请求上。它会检查用户角色是否与允许的值匹配,如果不匹配则返回 403 Forbidden。通过链式调用多个中间件函数,可以同时强制执行认证和授权。
错误处理与 Token 刷新
优雅地处理常见的认证场景。区分缺失的 token、无效的 token 和过期的 token,以提供清晰的反馈。可以考虑为长时间有效的会话实现刷新 token。
javascript
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'No token provided',
code: 'TOKEN_MISSING'
});
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired',
code: 'TOKEN_EXPIRED'
});
}
return res.status(401).json({
error: 'Invalid token',
code: 'TOKEN_INVALID'
});
}
};
带有错误代码的结构化错误响应有助于客户端适当地处理不同的失败模式。过期的 token 可能触发 token 刷新流程,而无效的 token 则需要重新进行认证。
课程总结
认证中间件将安全逻辑集中化,并在整个 API 中一致地应用。通过提取和验证 JWT token、将用户信息附加到请求上,以及实施基于角色的访问控制,你可以将开放的接口转变为一个安全的系统。请始终在授权之前先进行认证,返回清晰的错误响应,并考虑在生产环境部署中使用 token 过期和刷新策略。