Web 认证方案技术指南

25 阅读4分钟

本文档介绍 Web 应用中常见的认证方案,包括 Session-Cookie、JWT 和 SSO 单点登录,帮助开发者理解各方案的原理、差异及适用场景。

1. 认证方案概述

Web 认证的核心问题是:HTTP 是无状态协议,服务端如何识别用户身份?

graph LR
    A[用户] -->|请求| B[服务端]
    B -->|你是谁| A

    subgraph 解决方案
        C[Session Cookie]
        D[JWT Token]
        E[OAuth SSO]
    end

认证 vs 授权

概念英文解决的问题类比
认证Authentication你是谁?身份证
授权Authorization你能做什么?门禁卡

2. Session-Cookie 方案

2.1 工作原理

Session-Cookie 是传统的有状态认证方案,用户登录信息存储在服务端

sequenceDiagram  
    participant U as 用户  
    participant C as 客户端  
    participant S as 服务端  
    participant R as Redis/内存  
  
    U->>C: 输入账号密码  
    C->>S: POST /login  
    S->>S: 验证凭据  
    S->>R: 创建 Session<br/>存储用户信息  
    R-->>S: sessionId  
    S-->>C: Set-Cookie: sessionId=abc123  
  
    Note over C: Cookie 自动存储  
  
    U->>C: 请求数据  
    C->>S: GET /api/data<br/>Cookie: sessionId=abc123  
    S->>R: 查询 Session  
    R-->>S: 用户信息  
    S-->>C: 返回数据

2.2 代码示例

// 服务端 - Express + express-session
import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,      // 仅 HTTPS
    httpOnly: true,    // 防止 XSS 读取
    maxAge: 24 * 60 * 60 * 1000  // 24 小时
  }
}));

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

  if (user) {
    req.session.userId = user.id;
    req.session.role = user.role;
    res.json({ success: true });
  }
});

// 验证中间件
const authMiddleware = (req, res, next) => {
  if (req.session.userId) {
    next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
};

2.3 优缺点

优点缺点
服务端完全控制,可随时踢人需要 Session 存储(Redis/内存)
Session ID 较小分布式需要共享 Session
安全性较高(HttpOnly)有 CSRF 攻击风险
实现简单成熟跨域处理复杂
-移动端 Cookie 支持有限

3. JWT 方案

3.1 什么是 JWT

JWT(JSON Web Token)是一种无状态的认证方案,用户信息编码在 Token 中,存储在客户端

JWT 结构:Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.    <- Header (Base64)
eyJ1c2VySWQiOjEyMywiZXhwIjoxNjk5OTk5fQ.  <- Payload (Base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw <- Signature
flowchart LR
    subgraph Header
        A[alg HS256<br/>typ JWT]
    end

    subgraph Payload
        B[userId 123<br/>exp 1699999]
    end

    subgraph Signature
        C[HMACSHA256]
    end

    A --> D[Base64]
    B --> D
    C --> D
    D --> E[JWT Token]

3.2 工作原理

sequenceDiagram  
    participant U as 用户  
    participant C as 客户端  
    participant S as 服务端  
  
    U->>C: 输入账号密码  
    C->>S: POST /login  
    S->>S: 验证凭据  
    S->>S: 生成 JWT<br/>(包含用户信息 + 签名)  
    S-->>C: 返回 token  
  
    Note over C: 存储到 localStorage  
  
    U->>C: 请求数据  
    C->>C: 从 localStorage 读取 token  
    C->>S: GET /api/data<br/>Authorization: Bearer xxx  
    S->>S: 验证签名<br/>解析用户信息<br/>(无需查库)  
    S-->>C: 返回数据

3.3 Token 传递方式

方式安全性适用场景
Authorization Header推荐方案,API 请求
HttpOnly Cookie同域 Web 应用
URL Query Parameter仅用于下载链接、邮件验证等特殊场景
// 方式一:Authorization Header(推荐)
axios.get('/api/data', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

// 方式二:通过拦截器自动添加
axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`;
  }
  return config;
});

3.4 Token 刷新机制

由于 JWT 无法主动失效,通常采用双 Token 机制:

sequenceDiagram  
    participant C as 客户端  
    participant S as 服务端  
  
    Note over C,S: 登录时获取双 Token  
    C->>S: POST /login  
    S-->>C: accessToken (15分钟)<br/>refreshToken (7天)  
  
    Note over C,S: Access Token 过期  
    C->>S: GET /api/data (accessToken 过期)  
    S-->>C: 401 Unauthorized  
  
    Note over C,S: 使用 Refresh Token 刷新  
    C->>S: POST /refresh (refreshToken)  
    S->>S: 验证 refreshToken  
    S-->>C: 新的 accessToken  
  
    C->>S: GET /api/data (新 accessToken)  
    S-->>C: 返回数据
// 双 Token 刷新实现
interface TokenPair {
  accessToken: string;   // 短期,15分钟
  refreshToken: string;  // 长期,7天
}

// 响应拦截器 - 自动刷新
axios.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      const refreshToken = localStorage.getItem('refreshToken');

      try {
        const { data } = await axios.post('/auth/refresh', { refreshToken });
        localStorage.setItem('accessToken', data.accessToken);

        // 重试原请求
        error.config.headers['Authorization'] = `Bearer ${data.accessToken}`;
        return axios(error.config);
      } catch {
        // Refresh Token 也过期,跳转登录
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);

3.5 优缺点

优点缺点
无状态,天然支持分布式无法主动失效(需黑名单)
自包含用户信息,减少查库Token 较大(200+ 字节)
跨域友好续期机制较复杂
跨平台(Web/App/小程序)敏感信息不能放 Payload
微服务架构友好XSS 可窃取(存 localStorage)

4. Session vs JWT 对比

4.1 架构对比

flowchart TB  
    subgraph Session方案  
        A1[客户端] -->|Session ID| B1[服务端]  
        B1 -->|查询| C1[(Session 存储<br/>Redis/内存)]  
    end  
  
    subgraph JWT方案  
        A2[客户端] -->|JWT Token<br/>含用户信息| B2[服务端]  
        B2 -->|仅验证签名| B2  
    end

4.2 详细对比

特性Session-CookieJWT
状态存储服务端(Redis/内存)客户端(Token 自包含)
扩展性需要共享 Session 存储天然支持分布式
服务端压力每次请求查询 Session仅验证签名,无 IO
注销/踢人删除 Session 即可较难(需黑名单机制)
安全风险CSRF 攻击XSS 攻击
跨域支持需配置 Cookie天然支持
移动端Cookie 处理麻烦友好
Token 大小~6 字节(Session ID)~200+ 字节
实现复杂度简单中等(需处理刷新)

4.3 分布式场景对比

graph TB
    subgraph Session分布式问题
        U1[用户] --> LB1[负载均衡]

        LB1 --> S1[服务器 A<br/>存在 Session]
        LB1 --> S2[服务器 B<br/>无 Session]

        S1 -.-> R1[(共享 Redis)]
        S2 -.-> R1
    end
graph TB
    subgraph JWT无此问题
        U2[用户<br/>携带 Token] --> LB2[负载均衡]

        LB2 --> S3[服务器 A]
        LB2 --> S4[服务器 B]

        S3 --> V1[本地验证 Token]
        S4 --> V2[本地验证 Token]

        V1 --> N1[无需共享存储]
        V2 --> N1
    end

5. SSO 单点登录

5.1 什么是 SSO

SSO(Single Sign-On)单点登录:一次登录,多处访问

flowchart LR  
    subgraph 没有SSO  
        U1[用户] -->|登录| A1[系统 A]  
        U1 -->|再登录| B1[系统 B]  
        U1 -->|再登录| C1[系统 C]  
    end
graph TB
    subgraph SSO单点登录
        U2[用户] -->|登录一次| SSO[SSO认证中心]

        SSO -->|自动通行| A2[系统 A]
        SSO -->|自动通行| B2[系统 B]
        SSO -->|自动通行| C2[系统 C]
    end

5.2 常见场景

场景说明
Google 系登录 Gmail 后,YouTube、Drive 自动登录
阿里系登录淘宝后,天猫、支付宝自动登录
企业内网登录 OA 后,邮箱、CRM、ERP 都能访问
微信生态微信登录后,各小程序共享登录态

5.3 SSO 实现方案

方案一:共享 Cookie(同域)

适用于同一主域下的子系统。

flowchart TB
    subgraph example.com子域
        SSO[sso.example.com<br/>认证中心]
        A[a.example.com<br/>系统 A]
        B[b.example.com<br/>系统 B]
        C[c.example.com<br/>系统 C]
    end

    Cookie[Cookie 设置为 .example.com<br/>所有子域共享]

    SSO --> Cookie
    A --> Cookie
    B --> Cookie
    C --> Cookie
// 设置共享 Cookie
res.cookie('token', jwtToken, {
  domain: '.example.com',  // 主域名,所有子域共享
  httpOnly: true,
  secure: true
});

方案二:CAS 协议(跨域)

适用于不同域名的系统。

sequenceDiagram  
    participant U as 用户  
    participant A as 系统 A<br/>(app-a.com)  
    participant SSO as SSO 中心<br/>(sso.com)  
  
    Note over U,SSO: 首次访问系统 A  
    U->>A: 1. 访问系统 A  
    A->>A: 2. 检测未登录  
    A-->>U: 3. 重定向到 SSO  
    U->>SSO: 4. 跳转 SSO 登录页  
    U->>SSO: 5. 输入账号密码  
    SSO->>SSO: 6. 验证成功<br/>创建全局 Session  
    SSO-->>U: 7. 重定向回系统 A<br/>携带 ticket  
    U->>A: 8. 带 ticket 访问  
    A->>SSO: 9. 验证 ticket  
    SSO-->>A: 10. 返回用户信息  
    A->>A: 11. 创建局部 Session  
    A-->>U: 12. 登录成功
sequenceDiagram  
    participant U as 用户  
    participant B as 系统 B<br/>(app-b.com)  
    participant SSO as SSO 中心<br/>(sso.com)  
  
    Note over U,SSO: 访问系统 B(已在 SSO 登录)  
    U->>B: 1. 访问系统 B  
    B->>B: 2. 检测未登录  
    B-->>U: 3. 重定向到 SSO  
    U->>SSO: 4. 跳转 SSO  
    SSO->>SSO: 5. 检测已有全局 Session  
    SSO-->>U: 6. 直接返回 ticket<br/>(无需再登录)  
    U->>B: 7. 带 ticket 访问  
    B->>SSO: 8. 验证 ticket  
    SSO-->>B: 9. 返回用户信息  
    B-->>U: 10. 自动登录成功

方案三:OAuth 2.0 / OIDC

现代标准,适用于第三方登录和开放平台。

sequenceDiagram  
    participant U as 用户  
    participant App as 第三方应用  
    participant Auth as 授权服务器<br/>(如 GitHub)  
    participant API as 资源服务器  
  
    U->>App: 1. 点击&#34;GitHub 登录&#34;  
    App-->>U: 2. 重定向到 GitHub  
    U->>Auth: 3. 跳转 GitHub 授权页  
    U->>Auth: 4. 用户授权  
    Auth-->>U: 5. 重定向回 App<br/>携带 code  
    U->>App: 6. 带 code 访问  
    App->>Auth: 7. 用 code 换 token  
    Auth-->>App: 8. 返回 access_token  
    App->>API: 9. 用 token 获取用户信息  
    API-->>App: 10. 返回用户数据  
    App-->>U: 11. 登录成功

5.4 SSO 协议对比

协议特点适用场景
共享 Cookie简单直接同域系统
CAS经典企业方案企业内部系统
OAuth 2.0授权协议第三方登录、开放 API
OIDCOAuth 2.0 + 身份层现代标准方案
SAMLXML 格式,企业级传统企业、政府

5.5 SSO 与 JWT/Session 的关系

flowchart TB
    subgraph SSO架构方案
        SSO[SSO 单点登录]
    end

    subgraph 底层实现
        JWT[JWT Token]
        Session[Session Cookie]
    end

    SSO --> JWT
    SSO --> Session

    SSO --> Note[解决多系统共享登录]
    JWT --> Note2[解决单系统用户识别]
    Session --> Note2

6. 方案选型指南

6.1 决策流程图


flowchart TB  
    Start[开始选型] --> Q1{是否多系统?}  
  
    Q1 -->|是| Q2{是否同域?}  
    Q1 -->|否| Q3{是否分布式?}  
  
    Q2 -->|是| A1[共享 Cookie SSO]  
    Q2 -->|否| A2[CAS/OAuth SSO]  
  
    Q3 -->|是| Q4{需要即时踢人?}  
    Q3 -->|否| A3[Session-Cookie]  
  
    Q4 -->|是| A4[JWT + 黑名单]  
    Q4 -->|否| A5[纯 JWT]

6.2 场景推荐

场景推荐方案理由
单体应用Session-Cookie简单可靠,支持即时踢人
微服务架构JWT无状态,服务独立验证
移动端 AppJWT无 Cookie 限制
企业内部多系统SSO (CAS)统一认证管理
接入第三方登录OAuth 2.0 / OIDC行业标准
开放 API 平台OAuth 2.0 + JWT授权灵活,Token 自包含
高安全要求Session + 短期 Token可控性强

6.3 安全建议

flowchart LR  
    subgraph 防御措施  
        A[HTTPS 传输]  
        B[HttpOnly Cookie]  
        C[SameSite Cookie]  
        D[CSRF Token]  
        E[XSS 防护]  
        F[Token 过期机制]  
    end  
  
    A --> Safe[安全认证]  
    B --> Safe  
    C --> Safe  
    D --> Safe  
    E --> Safe  
    F --> Safe
攻击类型Session 方案JWT 方案
XSSHttpOnly 防护避免存 localStorage,或用 HttpOnly Cookie
CSRF需要 CSRF Token使用 Header 传 Token 天然防护
重放攻击Session 过期机制Token 过期 + Refresh Token
中间人HTTPSHTTPS

参考资料