Passport.js 高级主题 #
自定义策略 #
创建自定义策略 #
Passport.js 允许你创建自定义策略来满足特定的认证需求。
javascript
// strategies/ApiKeyStrategy.js
const passport = require('passport');
const util = require('util');
function ApiKeyStrategy(options, verify) {
if (typeof options === 'function') {
verify = options;
options = {};
}
if (!verify) {
throw new TypeError('ApiKeyStrategy requires a verify callback');
}
passport.Strategy.call(this);
this.name = 'apikey';
this._verify = verify;
this._headerField = options.headerField || 'x-api-key';
this._queryField = options.queryField || 'api_key';
}
util.inherits(ApiKeyStrategy, passport.Strategy);
ApiKeyStrategy.prototype.authenticate = function(req, options) {
const self = this;
// 从 Header 或 Query 获取 API Key
const apiKey = req.headers[this._headerField] ||
req.query[this._queryField];
if (!apiKey) {
return this.fail({ message: 'Missing API key' }, 400);
}
function verified(err, user, info) {
if (err) {
return self.error(err);
}
if (!user) {
return self.fail(info);
}
self.success(user, info);
}
try {
this._verify(apiKey, verified);
} catch (ex) {
return self.error(ex);
}
};
module.exports = ApiKeyStrategy;
使用自定义策略 #
javascript
// config/passport.js
const ApiKeyStrategy = require('../strategies/ApiKeyStrategy');
passport.use(new ApiKeyStrategy(
async (apiKey, done) => {
try {
const user = await User.findOne({ apiKey });
if (!user) {
return done(null, false, { message: 'Invalid API key' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// 路由中使用
router.get('/api/data', passport.authenticate('apikey', { session: false }), (req, res) => {
res.json({ data: 'protected data' });
});
自定义 Bearer Token 策略 #
javascript
// strategies/CustomBearerStrategy.js
const passport = require('passport');
const util = require('util');
function CustomBearerStrategy(verify) {
passport.Strategy.call(this);
this.name = 'custom-bearer';
this._verify = verify;
}
util.inherits(CustomBearerStrategy, passport.Strategy);
CustomBearerStrategy.prototype.authenticate = function(req) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return this.fail({ message: 'No authorization header' });
}
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
return this.fail({ message: 'Invalid authorization header format' });
}
const token = parts[1];
this._verify(token, (err, user, info) => {
if (err) return this.error(err);
if (!user) return this.fail(info);
this.success(user, info);
});
};
module.exports = CustomBearerStrategy;
多策略组合 #
同时支持多种认证方式 #
javascript
// 同时支持 JWT 和 API Key
router.get('/api/data',
passport.authenticate(['jwt', 'apikey'], { session: false }),
(req, res) => {
res.json({ data: 'protected data' });
}
);
条件认证中间件 #
javascript
// middleware/auth.js
function multiAuth(strategies, options = {}) {
return (req, res, next) => {
const authPromises = strategies.map(strategy => {
return new Promise((resolve) => {
passport.authenticate(strategy, { session: false }, (err, user, info) => {
if (err || !user) {
resolve({ success: false, strategy, info });
} else {
resolve({ success: true, user, strategy });
}
})(req, res, () => {});
});
});
Promise.all(authPromises)
.then(results => {
const success = results.find(r => r.success);
if (success) {
req.user = success.user;
req.authStrategy = success.strategy;
next();
} else {
res.status(401).json({
success: false,
message: 'Authentication failed',
details: results.map(r => ({
strategy: r.strategy,
message: r.info?.message
}))
});
}
})
.catch(next);
};
}
module.exports = { multiAuth };
javascript
// 使用多策略认证
const { multiAuth } = require('../middleware/auth');
router.get('/api/data', multiAuth(['jwt', 'apikey', 'basic']), (req, res) => {
res.json({
data: 'protected data',
authenticatedVia: req.authStrategy
});
});
优先级认证 #
javascript
// middleware/auth.js
function priorityAuth(strategies) {
return async (req, res, next) => {
for (const strategy of strategies) {
try {
const result = await new Promise((resolve) => {
passport.authenticate(strategy, { session: false }, (err, user) => {
resolve({ err, user });
})(req, res, () => {});
});
if (result.user) {
req.user = result.user;
req.authStrategy = strategy;
return next();
}
} catch (error) {
console.error(`Strategy ${strategy} error:`, error);
}
}
res.status(401).json({
success: false,
message: 'All authentication strategies failed'
});
};
}
// 使用优先级认证
router.get('/api/data', priorityAuth(['jwt', 'session', 'apikey']), (req, res) => {
res.json({ data: 'protected' });
});
动态策略选择 #
根据请求选择策略 #
javascript
// middleware/auth.js
function dynamicAuth() {
return (req, res, next) => {
let strategy;
// 根据请求特征选择策略
if (req.headers['authorization']?.startsWith('Bearer ')) {
strategy = 'jwt';
} else if (req.headers['x-api-key']) {
strategy = 'apikey';
} else if (req.session?.passport?.user) {
strategy = 'session';
} else {
return res.status(401).json({
success: false,
message: 'No valid authentication method found'
});
}
passport.authenticate(strategy, { session: false })(req, res, next);
};
}
// 使用动态策略
router.get('/api/data', dynamicAuth(), (req, res) => {
res.json({ data: 'protected' });
});
根据路由选择策略 #
javascript
// config/auth-strategies.js
const routeStrategies = {
'/api/public': null,
'/api/user': ['jwt', 'session'],
'/api/admin': ['jwt'],
'/api/service': ['apikey']
};
function routeBasedAuth(req, res, next) {
const path = req.path;
let strategies = null;
// 匹配路由
for (const [pattern, strategy] of Object.entries(routeStrategies)) {
if (path.startsWith(pattern)) {
strategies = strategy;
break;
}
}
// 无需认证
if (!strategies) {
return next();
}
// 使用对应策略
passport.authenticate(strategies, { session: false })(req, res, next);
}
app.use(routeBasedAuth);
匿名认证 #
可选认证 #
javascript
// middleware/auth.js
function optionalAuth(strategies) {
return (req, res, next) => {
passport.authenticate(strategies, { session: false }, (err, user) => {
if (err) return next(err);
if (user) {
req.user = user;
}
next();
})(req, res, next);
};
}
// 使用可选认证
router.get('/posts', optionalAuth('jwt'), async (req, res) => {
let posts;
if (req.user) {
// 已登录用户可以看到私有文章
posts = await Post.find({ $or: [
{ isPublic: true },
{ author: req.user._id }
]});
} else {
// 未登录用户只能看到公开文章
posts = await Post.find({ isPublic: true });
}
res.json(posts);
});
双因素认证 #
TOTP 策略 #
bash
npm install otplib
javascript
// strategies/TotpStrategy.js
const passport = require('passport');
const util = require('util');
const authenticator = require('otplib/authenticator');
function TotpStrategy(options, verify) {
passport.Strategy.call(this);
this.name = 'totp';
this._verify = verify;
this._window = options.window || 1;
}
util.inherits(TotpStrategy, passport.Strategy);
TotpStrategy.prototype.authenticate = function(req) {
const token = req.body.token || req.query.token;
const user = req.user;
if (!user) {
return this.fail({ message: 'User not authenticated' });
}
if (!token) {
return this.fail({ message: 'Missing TOTP token' });
}
this._verify(user, token, (err, success, info) => {
if (err) return this.error(err);
if (!success) return this.fail(info);
this.success(user, info);
});
};
module.exports = TotpStrategy;
javascript
// config/passport.js
const TotpStrategy = require('../strategies/TotpStrategy');
passport.use('totp', new TotpStrategy(
async (user, token, done) => {
try {
const isValid = authenticator.check(token, user.totpSecret);
if (!isValid) {
return done(null, false, { message: 'Invalid TOTP token' });
}
return done(null, true);
} catch (error) {
return done(error);
}
}
));
双因素认证流程 #
javascript
// routes/auth.js
// 启用双因素认证
router.post('/2fa/enable', ensureAuthenticated, async (req, res) => {
const secret = authenticator.generateSecret();
// 保存密钥(实际应用中应该加密存储)
req.user.totpSecret = secret;
req.user.totpEnabled = false; // 先不启用,等验证成功后再启用
await req.user.save();
// 生成二维码 URL
const otpauth = authenticator.keyuri(
req.user.username,
'MyApp',
secret
);
res.json({
secret,
otpauth,
qrCodeUrl: `https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=${encodeURIComponent(otpauth)}`
});
});
// 验证并启用双因素认证
router.post('/2fa/verify', ensureAuthenticated, (req, res, next) => {
passport.authenticate('totp', (err, success, info) => {
if (err) return next(err);
if (!success) {
return res.status(400).json({
success: false,
message: info.message
});
}
// 启用双因素认证
req.user.totpEnabled = true;
req.user.save();
res.json({ success: true });
})(req, res, next);
});
// 双因素认证登录
router.post('/login/2fa', ensureAuthenticated, (req, res, next) => {
if (!req.user.totpEnabled) {
return res.json({ success: true, user: req.user });
}
passport.authenticate('totp', (err, success, info) => {
if (err) return next(err);
if (!success) {
return res.status(400).json({
success: false,
message: info.message
});
}
res.json({ success: true, user: req.user });
})(req, res, next);
});
认证状态管理 #
认证状态中间件 #
javascript
// middleware/auth.js
function authState(req, res, next) {
req.authState = {
isAuthenticated: req.isAuthenticated(),
user: req.user || null,
strategy: req.session?.passport?.strategy || null,
timestamp: Date.now()
};
next();
}
// 使用认证状态
router.get('/auth/status', authState, (req, res) => {
res.json(req.authState);
});
认证事件钩子 #
javascript
// hooks/authHooks.js
const authEvents = new EventEmitter();
function authHook(req, res, next) {
const originalLogIn = req.logIn;
const originalLogOut = req.logout;
req.logIn = function(user, options, done) {
return originalLogIn.call(this, user, options, (err) => {
if (!err) {
authEvents.emit('login', {
user,
ip: req.ip,
userAgent: req.headers['user-agent'],
timestamp: new Date()
});
}
done?.(err);
});
};
req.logout = function(done) {
const user = req.user;
return originalLogOut.call(this, (err) => {
if (!err) {
authEvents.emit('logout', {
user,
ip: req.ip,
timestamp: new Date()
});
}
done?.(err);
});
};
next();
}
// 监听认证事件
authEvents.on('login', async (data) => {
console.log('User logged in:', data.user.username);
// 记录登录日志
await LoginLog.create(data);
});
authEvents.on('logout', async (data) => {
console.log('User logged out:', data.user.username);
});
module.exports = authHook;
权限控制 #
RBAC(基于角色的访问控制) #
javascript
// middleware/rbac.js
function checkPermission(resource, action) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'Unauthorized'
});
}
const role = req.user.role;
const permissions = {
admin: {
user: ['create', 'read', 'update', 'delete'],
post: ['create', 'read', 'update', 'delete'],
comment: ['create', 'read', 'update', 'delete']
},
editor: {
user: ['read'],
post: ['create', 'read', 'update'],
comment: ['create', 'read', 'update']
},
user: {
user: ['read'],
post: ['create', 'read'],
comment: ['create', 'read']
}
};
const rolePermissions = permissions[role];
if (!rolePermissions) {
return res.status(403).json({
success: false,
message: 'Invalid role'
});
}
const resourcePermissions = rolePermissions[resource];
if (!resourcePermissions || !resourcePermissions.includes(action)) {
return res.status(403).json({
success: false,
message: 'Permission denied'
});
}
next();
};
}
module.exports = { checkPermission };
javascript
// 使用权限控制
const { checkPermission } = require('../middleware/rbac');
router.post('/users',
authenticateJWT,
checkPermission('user', 'create'),
(req, res) => {
// 创建用户
}
);
router.delete('/posts/:id',
authenticateJWT,
checkPermission('post', 'delete'),
(req, res) => {
// 删除文章
}
);
资源所有权检查 #
javascript
// middleware/ownership.js
function checkOwnership(model, param = 'id', userField = 'author') {
return async (req, res, next) => {
try {
const resourceId = req.params[param];
const resource = await model.findById(resourceId);
if (!resource) {
return res.status(404).json({
success: false,
message: 'Resource not found'
});
}
// 管理员可以访问所有资源
if (req.user.role === 'admin') {
req.resource = resource;
return next();
}
// 检查所有权
const ownerId = resource[userField]._id || resource[userField];
if (ownerId.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
message: 'You do not have permission to access this resource'
});
}
req.resource = resource;
next();
} catch (error) {
next(error);
}
};
}
module.exports = { checkOwnership };
javascript
// 使用所有权检查
const { checkOwnership } = require('../middleware/ownership');
router.put('/posts/:id',
authenticateJWT,
checkOwnership(Post, 'id', 'author'),
(req, res) => {
// req.resource 已经包含文章
// 可以安全地更新
}
);
下一步 #
现在你已经掌握了高级主题,接下来学习 最佳实践,了解生产环境的安全建议和常见问题解决!
最后更新:2026-03-28