主题
认证与授权
认证 vs 授权
核心概念
- 认证(Authentication):验证用户身份,确认「你是谁」
- 授权(Authorization):验证用户权限,确认「你能做什么」
┌───────────────────────────────────────────────────┐
│ 认证与授权流程 │
├───────────────────┬───────────────────────────────┤
│ 1. 用户提交凭证 │ 用户名/密码、令牌、生物识别 │
│ 2. 认证 │ 验证凭证的真实性 │
│ 3. 生成身份标识 │ Session ID、JWT 等 │
│ 4. 授权检查 │ 基于身份和权限进行访问控制 │
│ 5. 访问资源 │ 允许或拒绝访问 │
└───────────────────┴───────────────────────────────┘Session-Cookie 认证
原理流程
- 用户登录:提交用户名和密码
- 服务器验证:验证凭证,创建 Session
- 存储 Session:将 Session 数据存储在服务器(内存、Redis 等)
- 返回 Cookie:将 Session ID 写入 Cookie
- 后续请求:浏览器自动携带 Cookie,服务器通过 Session ID 识别用户
服务端存储方案
| 存储方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 内存存储 | 速度快 | 重启丢失,无法水平扩展 | 开发环境,单实例 |
| Redis | 高性能,支持集群 | 需要额外依赖 | 生产环境,水平扩展 |
| 数据库 | 持久化 | 性能较差 | 小型应用,对性能要求不高 |
Session 配置
javascript
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
store: new RedisStore({ host: 'localhost', port: 6379 }),
cookie: {
httpOnly: true, // 防止 XSS
secure: process.env.NODE_ENV === 'production', // HTTPS 时启用
sameSite: 'strict', // 防止 CSRF
maxAge: 24 * 60 * 60 * 1000 // 24 小时
}
}));分布式 Session
- Session 共享:使用 Redis 等分布式存储
- Session 粘性:使用负载均衡器的粘性会话
- 无状态方案:使用 JWT 替代 Session
JWT 认证
JWT 结构
JWT(JSON Web Token)由三部分组成,用 . 分隔:
Header.Payload.Signature- Header:包含令牌类型和签名算法
- Payload:包含声明(claims),如用户 ID、过期时间等
- Signature:使用密钥对 Header 和 Payload 进行签名
签名算法
| 算法 | 描述 | 适用场景 |
|---|---|---|
| HS256 | HMAC-SHA256,对称加密 | 单服务,性能要求高 |
| RS256 | RSA-SHA256,非对称加密 | 多服务,需要密钥管理 |
| ES256 | ECDSA-SHA256,椭圆曲线加密 | 移动端,性能和安全平衡 |
双 Token 机制
- Access Token:短期令牌,用于访问受保护资源
- Refresh Token:长期令牌,用于刷新 Access Token
javascript
// 生成 Access Token
function generateAccessToken(user) {
return jwt.sign(
{ id: user.id, role: user.role },
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
}
// 生成 Refresh Token
function generateRefreshToken(user) {
return jwt.sign(
{ id: user.id },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
}令牌存储
| 存储位置 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cookie | 自动携带,安全配置 | 跨域限制 | 同域应用 |
| localStorage | 跨域支持 | 易受 XSS 攻击 | 单页应用 |
| sessionStorage | 会话级存储 | 页面刷新丢失 | 临时会话 |
JWT 安全注意事项
- 设置合理的过期时间:Access Token 短期,Refresh Token 长期
- 使用 HTTPS:防止令牌被窃取
- 实现令牌撤销:使用黑名单或版本号
- 验证签名:确保令牌未被篡改
- 避免敏感信息:Payload 中不要存储敏感数据
OAuth 2.0
核心概念
- 资源所有者:用户
- 客户端:第三方应用
- 授权服务器:颁发令牌
- 资源服务器:提供受保护资源
授权模式
| 模式 | 描述 | 适用场景 |
|---|---|---|
| 授权码模式 | 最安全,通过授权码获取令牌 | 服务器端应用 |
| 隐式模式 | 直接获取令牌,无授权码 | 单页应用 |
| 密码模式 | 直接使用用户名密码 | 第一方应用 |
| 客户端凭证模式 | 使用客户端 ID 和密钥 | 服务器间通信 |
授权码模式流程
- 用户请求授权:访问第三方应用
- 重定向到授权服务器:用户登录并授权
- 获取授权码:授权服务器返回授权码
- 交换令牌:客户端使用授权码换取令牌
- 访问资源:使用令牌访问受保护资源
PKCE 扩展
PKCE(Proof Key for Code Exchange)是 OAuth 2.0 的扩展,用于增强公共客户端的安全性,特别是移动应用和单页应用。
OpenID Connect (OIDC)
与 OAuth 2.0 的关系
OIDC 是建立在 OAuth 2.0 之上的身份认证层,添加了:
- ID Token:包含用户身份信息的 JWT
- UserInfo Endpoint:获取用户详细信息
- 标准化的身份认证流程
SSO 单点登录
OIDC 支持单点登录(SSO),用户只需登录一次,即可访问多个应用。
RBAC 权限模型
基本概念
RBAC(Role-Based Access Control)基于角色的访问控制:
- 用户(User):系统使用者
- 角色(Role):权限的集合
- 权限(Permission):对资源的操作许可
数据库设计
sql
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password VARCHAR(100)
);
-- 角色表
CREATE TABLE roles (
id INT PRIMARY KEY,
name VARCHAR(50) UNIQUE
);
-- 权限表
CREATE TABLE permissions (
id INT PRIMARY KEY,
name VARCHAR(50) UNIQUE,
resource VARCHAR(50),
action VARCHAR(20)
);
-- 用户角色关联表
CREATE TABLE user_roles (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- 角色权限关联表
CREATE TABLE role_permissions (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);权限检查中间件
javascript
function checkPermission(resource, action) {
return (req, res, next) => {
const userId = req.user.id;
// 从数据库或缓存获取用户权限
const hasPermission = userHasPermission(userId, resource, action);
if (!hasPermission) {
return res.status(403).json({ message: 'Forbidden' });
}
next();
};
}
// 使用
app.get('/admin/users', checkPermission('users', 'read'), (req, res) => {
// 处理请求
});ABAC 权限模型
基本概念
ABAC(Attribute-Based Access Control)基于属性的访问控制:
- 主体属性:用户的属性,如角色、部门、级别
- 资源属性:资源的属性,如类型、所有者、状态
- 环境属性:环境的属性,如时间、位置、设备
- 策略:基于属性的访问规则
策略示例
javascript
// 策略:部门经理可以查看本部门的所有文档
function canViewDocument(user, document, context) {
return (
user.role === 'manager' &&
user.department === document.department
);
}密码安全
密码存储
- 禁止明文存储:永远不要存储明文密码
- 使用安全哈希:使用 bcrypt、argon2 等算法
- 加盐:为每个密码添加唯一的盐值
- 工作因子:设置适当的计算成本
bcrypt 使用
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);
}密码强度策略
- 长度要求:至少 8 个字符
- 复杂度要求:包含大小写字母、数字、特殊字符
- 定期更换:提醒用户定期更换密码
- 限制尝试次数:防止暴力破解
单点登录 SSO
CAS 流程
CAS(Central Authentication Service)是一种常用的 SSO 实现:
- 用户访问应用:未登录时重定向到 CAS 服务器
- CAS 登录:用户在 CAS 服务器登录
- 生成 TGT:CAS 服务器生成 Ticket Granting Ticket
- 返回 ST:CAS 服务器返回 Service Ticket
- 应用验证:应用使用 ST 向 CAS 服务器验证
- 建立会话:应用为用户建立本地会话
基于 JWT 的 SSO
- 认证中心:统一的认证服务,颁发 JWT
- 应用集成:各应用验证 JWT 并建立本地会话
- 登出同步:通过消息队列或 Redis 实现登出同步
安全最佳实践
传输安全
- 使用 HTTPS:加密所有通信
- HTTP 严格传输安全(HSTS):强制使用 HTTPS
输入验证
- 验证所有输入:防止 SQL 注入、XSS 等攻击
- 使用参数化查询:防止 SQL 注入
令牌管理
- 定期轮换密钥:减少密钥泄露的风险
- 实现令牌撤销:支持用户登出和令牌失效
- 监控异常访问:检测可疑的令牌使用
日志与监控
- 记录认证事件:登录、登出、密码重置等
- 监控异常行为:多次失败尝试、异常访问模式
- 审计日志:记录关键操作,便于追溯
面试高频问题
1. Session 和 JWT 有什么区别?
回答思路:
- 存储位置:Session 存储在服务器,JWT 存储在客户端
- 状态管理:Session 是有状态的,JWT 是无状态的
- 扩展性:JWT 更适合水平扩展
- 安全性:Session 依赖 Cookie,JWT 依赖签名
- 撤销机制:Session 可直接删除,JWT 需要额外机制
- 适用场景:Session 适用于传统应用,JWT 适用于前后端分离和微服务
2. OAuth 2.0 有哪些授权模式?
回答思路:
- 授权码模式:最安全,通过授权码获取令牌,适用于服务器端应用
- 隐式模式:直接获取令牌,无授权码,适用于单页应用
- 密码模式:直接使用用户名密码,适用于第一方应用
- 客户端凭证模式:使用客户端 ID 和密钥,适用于服务器间通信
3. 如何防止 XSS 攻击?
回答思路:
- 输入验证:过滤和转义用户输入
- 输出编码:对输出进行 HTML 编码
- 内容安全策略(CSP):限制脚本来源
- HttpOnly Cookie:防止 JavaScript 访问 Cookie
- 使用现代框架:如 React、Vue 等,内置 XSS 防护
4. 如何防止 CSRF 攻击?
回答思路:
- CSRF Token:为每个表单添加 CSRF Token
- 验证 Origin/Referer:检查请求来源
- SameSite Cookie:设置 Cookie 的 SameSite 属性
- 使用 JWT:无状态认证,不依赖 Cookie
- 双重提交 Cookie:将 CSRF Token 同时放在 Cookie 和请求体中
5. 密码存储的最佳实践是什么?
回答思路:
- 使用安全哈希算法:如 bcrypt、argon2
- 加盐:为每个密码添加唯一的盐值
- 设置适当的工作因子:平衡安全性和性能
- 禁止存储明文密码:即使数据库泄露也不会暴露密码
- 定期提醒用户更换密码:减少密码被破解的风险
6. RBAC 和 ABAC 有什么区别?
回答思路:
- RBAC:基于角色的访问控制,权限与角色关联,简单易管理
- ABAC:基于属性的访问控制,权限与属性关联,更灵活
- 适用场景:RBAC 适用于权限结构相对固定的系统,ABAC 适用于权限规则复杂的系统
- 扩展性:ABAC 比 RBAC 更灵活,但也更复杂
7. 如何实现分布式 Session?
回答思路:
- 使用 Redis:将 Session 存储在 Redis 中,支持集群
- Session 粘性:使用负载均衡器的粘性会话,确保同一用户的请求发送到同一服务器
- 无状态方案:使用 JWT 替代 Session,完全无状态
- 一致性哈希:在 Redis 集群中使用一致性哈希,提高可用性
8. JWT 如何实现令牌撤销?
回答思路:
- 黑名单:将已撤销的令牌存储在 Redis 中,验证时检查
- 短期令牌:设置较短的过期时间,减少撤销的影响
- 版本号:在令牌中包含版本号,撤销时更新版本号
- 刷新机制:使用 Refresh Token,当需要撤销时,使 Refresh Token 失效
延伸阅读
官方文档
书籍
- 《OAuth 2.0 in Action》by Justin Richer and Antonio Sanso
- 《Web Application Security: A Beginner's Guide》by Bryan Sullivan and Vincent Liu
- 《Security Engineering: A Guide to Building Dependable Distributed Systems》by Ross Anderson
在线资源
工具
- jsonwebtoken:Node.js JWT 库
- passport:Node.js 认证中间件
- bcrypt:密码哈希库
- Helmet:Express 安全中间件