Skip to content

认证流程详解

text
相关代码: server/sessionManager.ts, server/index.ts, src/hooks/useAuth.tsx

整体流程

Session Token 结构

格式

{base64url_payload}.{signature}

Payload

json
{
  "username": "admin",
  "exp": 1705209600000
}

签名

HMAC-SHA256(payload, SECRET)

服务端实现

创建 Session

typescript
// server/sessionManager.ts
createSession(username: string): string {
  const session = {
    username,
    exp: Date.now() + TTL_HOURS * 60 * 60 * 1000
  }
  const payload = Buffer.from(JSON.stringify(session)).toString('base64url')
  const signature = sign(payload)
  return `${payload}.${signature}`
}

验证 Session

typescript
verifySession(token: string): Session | null {
  const [payload, signature] = token.split('.')

  // 1. 验证签名
  if (sign(payload) !== signature) {
    return null
  }

  // 2. 解析并检查过期
  const session = JSON.parse(Buffer.from(payload, 'base64url').toString())
  if (session.exp < Date.now()) {
    return null
  }

  return session
}

中间件

typescript
// server/index.ts
function requireAuth(req, res, next) {
  const cookies = parseCookies(req.headers.cookie)
  const session = cookies.session
    ? sessionManager.verifySession(cookies.session)
    : null

  if (!session) {
    res.status(401).json({ error: 'Unauthorized' })
    return
  }

  next()
}

前端实现

AuthProvider

tsx
// src/hooks/useAuth.tsx
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  // 页面加载时检查登录状态
  useEffect(() => {
    fetch('/api/v1/auth/check', { credentials: 'include' })
      .then(res => res.json())
      .then(data => {
        if (data.authenticated) {
          setUser({ username: data.username })
        }
      })
      .finally(() => setLoading(false))
  }, [])

  const login = async (credentials) => {
    const res = await fetch('/api/v1/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials),
      credentials: 'include'  // 重要:携带 Cookie
    })

    if (res.ok) {
      const data = await res.json()
      setUser({ username: data.username })
      return true
    }
    return false
  }

  // ...
}

ProtectedRoute

tsx
// src/components/ProtectedRoute.tsx
export default function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useAuth()

  if (loading) {
    return <Spin />
  }

  if (!isAuthenticated) {
    return <Navigate to="/login" />
  }

  return children
}

API 端点

端点方法说明
/api/v1/loginPOST登录
/api/v1/logoutPOST登出
/api/v1/auth/checkGET检查登录状态

登录请求

bash
POST /api/v1/login
Content-Type: application/json

{
  "username": "admin",
  "password": "xxx"
}

登录响应

bash
HTTP/1.1 200 OK
Set-Cookie: session=xxx; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400

{
  "success": true,
  "username": "admin"
}

配置项

环境变量默认值说明
VITE_ADMIN_USERNAMEadmin登录用户名
VITE_ADMIN_PASSWORDchangeme登录密码
SESSION_TTL_HOURS24会话有效期(小时)
SESSION_SECRET随机生成HMAC 密钥
COOKIE_SECUREfalseHTTPS 时设为 true

安全设计

属性说明
HttpOnlytrue禁止 JS 访问
SameSiteLax防止 CSRF
Secure根据环境HTTPS 时启用
Path/全站有效

无状态验证

  • 不需要服务端存储 session
  • 通过签名验证有效性
  • 通过 exp 检查过期

已知限制

  1. 单用户 - 当前只支持一个管理员账号
  2. 无密码加密 - 密码以明文存储在环境变量
  3. 无重试限制 - 没有登录失败锁定