登录改用访问令牌 + 刷新令牌,不用 cookie

- 访问令牌:JWT(HS256),15 分钟,Authorization: Bearer;WebSocket 放在网址参数里
- 刷新令牌:30 天,每次刷新换新的,数据库只存哈希;退出登录即作废
- 重置密码、删除学生:令牌版本加一并删除刷新令牌,所有设备立即退出
- 签名密钥首次使用时生成,存在数据库 settings 表
- 浏览器:令牌存 localStorage;快过期先刷新,401 时刷新一次再重试;多个标签页同时刷新不冲突

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 18:20:41 +08:00
1 parent a3eb563b70
commit 5bb2940969
11 files changed
+332 -110

No files matched your search

+19 -14
View File
@@ -1,4 +1,5 @@
import { reactive, shallowReactive } from 'vue'
import { validAccessToken } from '../tokens'
import type {
BoardState,
ClientMessage,
@@ -43,20 +44,24 @@ export function createRunSession() {
let actionSentAt = 0
function connect() {
connecting ??= new Promise<void>((resolve) => {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
const ws = new WebSocket(`${protocol}://${location.host}/ws`)
socket = ws
ws.onmessage = (event) => handle(JSON.parse(event.data) as ServerMessage)
ws.onopen = () => resolve()
ws.onclose = () => {
state.status = 'stopped'
state.board = null
socket = undefined
connecting = undefined
resolve()
}
})
connecting ??= validAccessToken().then(
(token) =>
new Promise<void>((resolve) => {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
// 浏览器的 WebSocket 不能带请求头,访问令牌放在网址参数里(只在建立连接时检查)
const ws = new WebSocket(`${protocol}://${location.host}/ws?token=${encodeURIComponent(token ?? '')}`)
socket = ws
ws.onmessage = (event) => handle(JSON.parse(event.data) as ServerMessage)
ws.onopen = () => resolve()
ws.onclose = () => {
state.status = 'stopped'
state.board = null
socket = undefined
connecting = undefined
resolve()
}
}),
)
return connecting
}