Appearance
认证与授权
认证(Authentication)和授权(Authorization)是网络安全的两个核心概念。认证验证用户的身份,而授权确定用户有权访问哪些资源。
认证 vs 授权
认证 (Authentication)
认证是验证用户身份的过程。系统需要确认用户确实是他们声称的那个人。
常见的认证方式:
- 用户名和密码
- 多因素认证 (MFA)
- 生物识别
- 证书认证
授权 (Authorization)
授权是在认证成功后,确定用户有权访问哪些资源或执行哪些操作的过程。
常见的授权机制:
- 基于角色的访问控制 (RBAC)
- 基于属性的访问控制 (ABAC)
- 访问控制列表 (ACL)
认证机制
1. 基于会话的认证
传统的 Web 应用通常使用基于会话的认证:
javascript
// 登录处理
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (user && await bcrypt.compare(password, user.passwordHash)) {
// 创建会话
req.session.userId = user.id;
req.session.authenticated = true;
res.json({ success: true, message: 'Login successful' });
} else {
res.status(401).json({ success: false, message: 'Invalid credentials' });
}
});
// 会话验证中间件
const requireAuth = (req, res, next) => {
if (req.session.authenticated) {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
2. JWT (JSON Web Token) 认证
JWT 是一种无状态的认证机制:
javascript
const jwt = require('jsonwebtoken');
// 生成 JWT
function generateToken(user) {
const payload = {
userId: user.id,
username: user.username,
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1小时后过期
};
return jwt.sign(payload, process.env.JWT_SECRET);
}
// 验证 JWT
function verifyToken(token) {
try {
return jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
throw new Error('Invalid token');
}
}
// JWT 认证中间件
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
3. OAuth 2.0
OAuth 2.0 是一种授权框架,常用于第三方登录:
javascript
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, async (accessToken, refreshToken, profile, done) => {
// 查找或创建用户
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value
});
}
return done(null, user);
}));
授权机制
基于角色的访问控制 (RBAC)
javascript
// 角色定义
const ROLES = {
ADMIN: 'admin',
MODERATOR: 'moderator',
USER: 'user'
};
// 权限定义
const PERMISSIONS = {
READ_USERS: 'read:users',
WRITE_USERS: 'write:users',
DELETE_USERS: 'delete:users'
};
// 角色权限映射
const ROLE_PERMISSIONS = {
[ROLES.ADMIN]: [PERMISSIONS.READ_USERS, PERMISSIONS.WRITE_USERS, PERMISSIONS.DELETE_USERS],
[ROLES.MODERATOR]: [PERMISSIONS.READ_USERS, PERMISSIONS.WRITE_USERS],
[ROLES.USER]: [PERMISSIONS.READ_USERS]
};
// 授权中间件
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const userPermissions = ROLE_PERMISSIONS[req.user.role] || [];
if (!userPermissions.includes(permission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// 使用示例
app.delete('/users/:id',
authenticateJWT,
requirePermission(PERMISSIONS.DELETE_USERS),
deleteUserHandler
);
基于属性的访问控制 (ABAC)
javascript
// ABAC 策略引擎
class PolicyEngine {
evaluate(policy, user, resource, action) {
// 简化的策略评估
if (policy.subject && policy.subject !== user.role) {
return false;
}
if (policy.action && policy.action !== action) {
return false;
}
if (policy.resource && !this.matchResource(policy.resource, resource)) {
return false;
}
// 环境条件检查
if (policy.conditions) {
for (const condition of policy.conditions) {
if (!this.evaluateCondition(condition, user, resource)) {
return false;
}
}
}
return true;
}
matchResource(pattern, resource) {
// 简化的资源匹配
return pattern === resource.type;
}
evaluateCondition(condition, user, resource) {
// 简化的条件评估
if (condition.type === 'owner' && condition.attribute === 'userId') {
return user.id === resource.userId;
}
return true;
}
}
// 使用示例
const policy = {
subject: 'user',
action: 'read',
resource: 'document',
conditions: [{
type: 'owner',
attribute: 'userId'
}]
};
安全最佳实践
1. 密码安全
javascript
const bcrypt = require('bcrypt');
// 密码哈希
async function hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
// 密码验证
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// 密码强度验证
function validatePasswordStrength(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar;
}
2. 速率限制
javascript
const rateLimit = require('express-rate-limit');
// 登录尝试限制
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 5, // 最多5次尝试
message: 'Too many login attempts, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
// API 调用限制
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 最多100次请求
skipSuccessfulRequests: true
});
3. 会话管理
javascript
const session = require('express-session');
const MongoStore = require('connect-mongo');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI
}),
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only
httpOnly: true, // 防止 XSS
maxAge: 1800000, // 30分钟
sameSite: 'strict' // 防止 CSRF
}
}));
// 会话轮换
function rotateSession(req, res, next) {
const oldSession = { ...req.session };
req.session.regenerate((err) => {
if (err) {
return next(err);
}
// 保留重要会话数据
Object.keys(oldSession).forEach(key => {
if (['userId', 'role'].includes(key)) {
req.session[key] = oldSession[key];
}
});
next();
});
}
常见安全漏洞及防护
1. Session Fixation
javascript
// 登录后轮换会话 ID
app.post('/login', (req, res) => {
req.session.regenerate((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
// 设置认证状态
req.session.userId = user.id;
req.session.authenticated = true;
res.json({ success: true });
});
});
2. 权限提升
javascript
// 永远不要信任客户端传来的角色信息
app.put('/users/:id', authenticateJWT, async (req, res) => {
const { id } = req.params;
const { role } = req.body; // 不要直接使用!
// 从数据库获取当前用户信息
const currentUser = await User.findById(req.user.userId);
// 只有管理员才能修改用户角色
if (currentUser.role !== 'admin') {
return res.status(403).json({ error: 'Insufficient permissions' });
}
// 安全地更新用户信息
await User.findByIdAndUpdate(id, {
$unset: { role: '' } // 不直接使用 req.body.role
});
});
前端安全考虑
1. 令牌存储
javascript
// 不要在 localStorage 中存储敏感令牌(易受 XSS 攻击)
// 使用 httpOnly Cookie 或内存存储
// 内存存储示例
class AuthManager {
constructor() {
this.token = null;
}
setToken(token) {
this.token = token;
}
getToken() {
return this.token;
}
clearToken() {
this.token = null;
}
}
// 自动刷新令牌
async function refreshTokenIfNeeded() {
const token = authManager.getToken();
if (token && isTokenExpired(token)) {
try {
const newToken = await fetchRefreshToken();
authManager.setToken(newToken);
} catch (error) {
logout();
}
}
}
2. 请求安全
javascript
// 添加认证头部
const apiClient = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json'
}
});
apiClient.interceptors.request.use(
(config) => {
const token = authManager.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 处理认证失败
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
logout();
}
return Promise.reject(error);
}
);
总结
认证和授权是 Web 应用安全的基石。实现时需要注意:
- 使用成熟的安全库和框架
- 实施多层安全防护
- 定期审查和更新安全策略
- 监控和记录安全事件
- 保持系统和依赖项的更新