跳到主要内容

安全认证

本章节介绍 Express.js 应用中的安全措施和认证授权实现。

安全中间件

Helmet

设置各种 HTTP 头来保护应用:

npm install helmet
const helmet = require('helmet');

app.use(helmet());

app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "trusted-cdn.com"],
},
},
}));

CORS

跨域资源共享配置:

npm install cors
const cors = require('cors');

app.use(cors());

app.use(cors({
origin: ['https://example.com', 'https://api.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));

限流

防止暴力攻击:

npm install express-rate-limit
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { error: '请求过于频繁,请稍后再试' }
});

app.use('/api', limiter);

const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: { error: '登录尝试过多,请稍后再试' }
});

app.post('/login', authLimiter, loginHandler);

认证方式

Session 认证

npm install express-session
const session = require('express-session');

app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000
}
}));

app.post('/login', (req, res) => {
const { username, password } = req.body;

if (validateUser(username, password)) {
req.session.userId = user.id;
res.json({ message: '登录成功' });
} else {
res.status(401).json({ error: '用户名或密码错误' });
}
});

app.get('/logout', (req, res) => {
req.session.destroy();
res.json({ message: '已登出' });
});

JWT 认证

npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const generateToken = (user) => {
return jwt.sign(
{ id: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
};

const verifyToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: '请先登录' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Token 无效或已过期' });
}
};

app.post('/register', async (req, res) => {
const { username, password } = req.body;

const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ username, password: hashedPassword });

const token = generateToken(user);
res.status(201).json({ user, token });
});

app.post('/login', async (req, res) => {
const { username, password } = req.body;

const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: '用户不存在' });
}

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).json({ error: '密码错误' });
}

const token = generateToken(user);
res.json({ user, token });
});

app.get('/profile', verifyToken, (req, res) => {
res.json({ user: req.user });
});

Passport.js

npm install passport passport-local passport-jwt
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;

passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
}, async (email, password, done) => {
try {
const user = await User.findOne({ email });
if (!user) return done(null, false, { message: '用户不存在' });

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return done(null, false, { message: '密码错误' });

return done(null, user);
} catch (err) {
return done(err);
}
}));

passport.use(new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
}, async (payload, done) => {
try {
const user = await User.findById(payload.id);
if (user) return done(null, user);
return done(null, false);
} catch (err) {
return done(err, false);
}
}));

app.use(passport.initialize());

app.post('/login', passport.authenticate('local', { session: false }), (req, res) => {
const token = generateToken(req.user);
res.json({ user: req.user, token });
});

app.get('/profile',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.json({ user: req.user });
}
);

权限控制

角色中间件

const checkRole = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: '请先登录' });
}

if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '权限不足' });
}

next();
};
};

app.delete('/users/:id',
verifyToken,
checkRole('admin'),
deleteUser
);

资源所有权检查

const checkOwnership = (model) => {
return async (req, res, next) => {
const resource = await model.findById(req.params.id);

if (!resource) {
return res.status(404).json({ error: '资源不存在' });
}

if (resource.userId.toString() !== req.user.id) {
return res.status(403).json({ error: '无权访问此资源' });
}

req.resource = resource;
next();
};
};

app.put('/posts/:id',
verifyToken,
checkOwnership(Post),
updatePost
);

安全最佳实践

  1. 使用 HTTPS:生产环境必须使用 HTTPS
  2. 环境变量:敏感信息存储在环境变量中
  3. 输入验证:验证所有用户输入
  4. SQL 注入防护:使用 ORM 或参数化查询
  5. XSS 防护:转义用户输入,使用 helmet
  6. CSRF 防护:使用 csurf 中间件
  7. 密码加密:使用 bcrypt 存储密码
  8. 日志记录:记录安全相关事件