Skip to content

认证与授权

认证 vs 授权

核心概念

  • 认证(Authentication):验证用户身份,确认「你是谁」
  • 授权(Authorization):验证用户权限,确认「你能做什么」
┌───────────────────────────────────────────────────┐
│                认证与授权流程                    │
├───────────────────┬───────────────────────────────┤
│ 1. 用户提交凭证    │ 用户名/密码、令牌、生物识别   │
│ 2. 认证           │ 验证凭证的真实性             │
│ 3. 生成身份标识    │ Session ID、JWT 等         │
│ 4. 授权检查       │ 基于身份和权限进行访问控制   │
│ 5. 访问资源       │ 允许或拒绝访问              │
└───────────────────┴───────────────────────────────┘

原理流程

  1. 用户登录:提交用户名和密码
  2. 服务器验证:验证凭证,创建 Session
  3. 存储 Session:将 Session 数据存储在服务器(内存、Redis 等)
  4. 返回 Cookie:将 Session ID 写入 Cookie
  5. 后续请求:浏览器自动携带 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
  1. Header:包含令牌类型和签名算法
  2. Payload:包含声明(claims),如用户 ID、过期时间等
  3. Signature:使用密钥对 Header 和 Payload 进行签名

签名算法

算法描述适用场景
HS256HMAC-SHA256,对称加密单服务,性能要求高
RS256RSA-SHA256,非对称加密多服务,需要密钥管理
ES256ECDSA-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 和密钥服务器间通信

授权码模式流程

  1. 用户请求授权:访问第三方应用
  2. 重定向到授权服务器:用户登录并授权
  3. 获取授权码:授权服务器返回授权码
  4. 交换令牌:客户端使用授权码换取令牌
  5. 访问资源:使用令牌访问受保护资源

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 实现:

  1. 用户访问应用:未登录时重定向到 CAS 服务器
  2. CAS 登录:用户在 CAS 服务器登录
  3. 生成 TGT:CAS 服务器生成 Ticket Granting Ticket
  4. 返回 ST:CAS 服务器返回 Service Ticket
  5. 应用验证:应用使用 ST 向 CAS 服务器验证
  6. 建立会话:应用为用户建立本地会话

基于 JWT 的 SSO

  1. 认证中心:统一的认证服务,颁发 JWT
  2. 应用集成:各应用验证 JWT 并建立本地会话
  3. 登出同步:通过消息队列或 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

在线资源

工具

用心学习,用代码说话 💻