Skip to content

认证与授权 ​

JWT (JSON Web Token) ​

结构 ​

header.payload.signature

header:  {"alg": "HS256", "typ": "JWT"}
payload: {"sub": "user123", "iat": 1516239022, "exp": 1516242622}
signature: HMACSHA256(base64(header) + "." + base64(payload), secret)
header.payload.signature

header:  {"alg": "HS256", "typ": "JWT"}
payload: {"sub": "user123", "iat": 1516239022, "exp": 1516242622}
signature: HMACSHA256(base64(header) + "." + base64(payload), secret)

使用流程 ​

客户端                      服务端
  │                          │
  ├─ POST /login ──────────► │  用户名密码
  │                          │
  │ ◄── JWT Token ──────────┤  服务端验证并签发
  │                          │
  ├─ GET /api/data ─────────► │  请求头: Authorization: Bearer <token>
  │                          │
  │ ◄── 200 OK ─────────────┤  服务端验证 Token 后返回数据
客户端                      服务端
  │                          │
  ├─ POST /login ──────────► │  用户名密码
  │                          │
  │ ◄── JWT Token ──────────┤  服务端验证并签发
  │                          │
  ├─ GET /api/data ─────────► │  请求头: Authorization: Bearer <token>
  │                          │
  │ ◄── 200 OK ─────────────┤  服务端验证 Token 后返回数据

Token 存储 ​

js
// ❌ localStorage(XSS 可读取)
localStorage.setItem('token', jwt)

// ✅ HttpOnly Cookie(防 XSS 窃取)
document.cookie = `token=${jwt}; HttpOnly; Secure; SameSite=Lax; Path=/`

// ✅ 内存存储(最安全,但刷新丢失)
let token = null
export function setToken(t) { token = t }
export function getToken() { return token }
// ❌ localStorage(XSS 可读取)
localStorage.setItem('token', jwt)

// ✅ HttpOnly Cookie(防 XSS 窃取)
document.cookie = `token=${jwt}; HttpOnly; Secure; SameSite=Lax; Path=/`

// ✅ 内存存储(最安全,但刷新丢失)
let token = null
export function setToken(t) { token = t }
export function getToken() { return token }

刷新机制 ​

js
// access token(短期,15分钟)+ refresh token(长期,7天)
async function refreshToken() {
  const res = await fetch('/api/auth/refresh', {
    credentials: 'include', // 自动携带 refresh token cookie
  })
  if (res.ok) {
    const { accessToken } = await res.json()
    setToken(accessToken)
    return accessToken
  }
  // 刷新失败,跳转登录
  redirectToLogin()
}

// axios 拦截器自动处理
api.interceptors.response.use(
  res => res,
  async (error) => {
    if (error.response?.status === 401) {
      const newToken = await refreshToken()
      error.config.headers.Authorization = `Bearer ${newToken}`
      return api(error.config) // 重试原请求
    }
    return Promise.reject(error)
  }
)
// access token(短期,15分钟)+ refresh token(长期,7天)
async function refreshToken() {
  const res = await fetch('/api/auth/refresh', {
    credentials: 'include', // 自动携带 refresh token cookie
  })
  if (res.ok) {
    const { accessToken } = await res.json()
    setToken(accessToken)
    return accessToken
  }
  // 刷新失败,跳转登录
  redirectToLogin()
}

// axios 拦截器自动处理
api.interceptors.response.use(
  res => res,
  async (error) => {
    if (error.response?.status === 401) {
      const newToken = await refreshToken()
      error.config.headers.Authorization = `Bearer ${newToken}`
      return api(error.config) // 重试原请求
    }
    return Promise.reject(error)
  }
)

OAuth 2.0 ​

授权码流程(Authorization Code) ​

用户 ───→ 第三方应用 ───→ 授权服务器 ───→ 资源服务器
  │           │              │                │
  │   1. 请求登录            │                │
  │◄─── 跳转授权页 ────────┤                │
  │   2. 用户授权           │                │
  ├─────────────────►       │                │
  │◄─── 授权码 ────────────┤                │
  │   3. 换 Token           │                │
  │           ├─────────────►                │
  │           │◄──── access_token ──────────┤
  │   4. 获取资源           │                │
  │           ├─────────────────────────────►
  │           │◄────────── 数据 ────────────┤
用户 ───→ 第三方应用 ───→ 授权服务器 ───→ 资源服务器
  │           │              │                │
  │   1. 请求登录            │                │
  │◄─── 跳转授权页 ────────┤                │
  │   2. 用户授权           │                │
  ├─────────────────►       │                │
  │◄─── 授权码 ────────────┤                │
  │   3. 换 Token           │                │
  │           ├─────────────►                │
  │           │◄──── access_token ──────────┤
  │   4. 获取资源           │                │
  │           ├─────────────────────────────►
  │           │◄────────── 数据 ────────────┤

GitHub OAuth 示例 ​

js
// 1. 跳转授权
const clientId = 'your_client_id'
const redirectUri = 'https://example.com/auth/callback'
window.location.href = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=user`

// 2. 回调中换 token
async function handleCallback(code) {
  const res = await fetch('https://github.com/login/oauth/access_token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({ client_id, client_secret, code }),
  })
  const { access_token } = await res.json()
  return access_token
}

// 3. 获取用户信息
const userRes = await fetch('https://api.github.com/user', {
  headers: { Authorization: `Bearer ${access_token}` },
})
// 1. 跳转授权
const clientId = 'your_client_id'
const redirectUri = 'https://example.com/auth/callback'
window.location.href = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=user`

// 2. 回调中换 token
async function handleCallback(code) {
  const res = await fetch('https://github.com/login/oauth/access_token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({ client_id, client_secret, code }),
  })
  const { access_token } = await res.json()
  return access_token
}

// 3. 获取用户信息
const userRes = await fetch('https://api.github.com/user', {
  headers: { Authorization: `Bearer ${access_token}` },
})

SSO (Single Sign-On) ​

原理 ​

用户 ───→ 应用A ───→ CAS 服务器 ───→ 应用B
  │        │              │              │
  ├────────►              │              │
  │ 未登录 │              │              │
  │◄─────── 跳转登录页 ──┤              │
  ├──────────────────────►              │
  │◄─────── 票据(ticket)────────────┤
  │        ├──────────────►              │
  │        │◄─── 验证通过 ──────────────┤
  │        │ 已登录,无需重复登录         │
  │        │              │              ├────────►
  │        │              │              │ 携带票据
  │        │              │◄─────────────┤
  │        │              ├────── 验证 ──►
  │        │              │◄─── 登录成功 ─┤
  │        │              │              │
用户 ───→ 应用A ───→ CAS 服务器 ───→ 应用B
  │        │              │              │
  ├────────►              │              │
  │ 未登录 │              │              │
  │◄─────── 跳转登录页 ──┤              │
  ├──────────────────────►              │
  │◄─────── 票据(ticket)────────────┤
  │        ├──────────────►              │
  │        │◄─── 验证通过 ──────────────┤
  │        │ 已登录,无需重复登录         │
  │        │              │              ├────────►
  │        │              │              │ 携带票据
  │        │              │◄─────────────┤
  │        │              ├────── 验证 ──►
  │        │              │◄─── 登录成功 ─┤
  │        │              │              │

实现方式 ​

方式原理适用
同域名 Cookie共享根域名 Cookie统一子域名
CAS (Central Auth Service)统一认证中心跨域多系统
JWT + 共享密钥Token 在各系统间传递前后端分离
OAuth 2.0授权码模式第三方登录

常见安全问题 ​

js
// 1. 密码明文传输
fetch('/api/login', {
  method: 'POST',
  body: JSON.stringify({ password: '123456' }),
  // ✅ 应该使用 HTTPS + 密码加密
})

// 2. Token 不过期
// ✅ access token 15分钟,refresh token 7天

// 3. 缺少速率限制
// ✅ 登录接口加验证码 + 速率限制

// 4. 用户ID 枚举
// GET /api/user/123 递增ID
// ✅ 使用 UUID,而不是自增ID
// 1. 密码明文传输
fetch('/api/login', {
  method: 'POST',
  body: JSON.stringify({ password: '123456' }),
  // ✅ 应该使用 HTTPS + 密码加密
})

// 2. Token 不过期
// ✅ access token 15分钟,refresh token 7天

// 3. 缺少速率限制
// ✅ 登录接口加验证码 + 速率限制

// 4. 用户ID 枚举
// GET /api/user/123 递增ID
// ✅ 使用 UUID,而不是自增ID