主题
认证流程详解
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/login | POST | 登录 |
/api/v1/logout | POST | 登出 |
/api/v1/auth/check | GET | 检查登录状态 |
登录请求
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_USERNAME | admin | 登录用户名 |
VITE_ADMIN_PASSWORD | changeme | 登录密码 |
SESSION_TTL_HOURS | 24 | 会话有效期(小时) |
SESSION_SECRET | 随机生成 | HMAC 密钥 |
COOKIE_SECURE | false | HTTPS 时设为 true |
安全设计
Cookie 属性
| 属性 | 值 | 说明 |
|---|---|---|
HttpOnly | true | 禁止 JS 访问 |
SameSite | Lax | 防止 CSRF |
Secure | 根据环境 | HTTPS 时启用 |
Path | / | 全站有效 |
无状态验证
- 不需要服务端存储 session
- 通过签名验证有效性
- 通过
exp检查过期
已知限制
- 单用户 - 当前只支持一个管理员账号
- 无密码加密 - 密码以明文存储在环境变量
- 无重试限制 - 没有登录失败锁定