Fix auth security: add request timeout, safe redirects, and memory-only token support.

This commit is contained in:
2026-03-18 14:55:37 +08:00
parent 2b1a2f0c28
commit 257668f3f3
6 changed files with 126 additions and 83 deletions
+23 -20
View File
@@ -2,6 +2,9 @@
import Brand from '@/components/Brand.vue'
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import { api } from '@/utils/request'
import { ApiError } from '@/types/api'
import type { UserInfo } from '@/stores/user'
const formState = reactive({
username: '',
@@ -29,33 +32,33 @@ const handleSubmit = async () => {
await formRef.value.validate()
loading.value = true
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: formState.username,
password: formState.password,
}),
const data = await api.post<{
access_token: string
refresh_token: string
user: UserInfo
}>('/api/auth/login', {
username: formState.username,
password: formState.password,
})
const data = await response.json()
userStore.setToken(data.access_token, data.refresh_token, formState.remember)
userStore.setUser(data.user)
if (data.code === 0) {
userStore.setToken(data.data.access_token, data.data.refresh_token)
userStore.setUser(data.data.user)
message.success('登录成功!')
const redirect = (route.query.redirect as string) || '/'
setTimeout(() => {
router.push(redirect)
}, 500)
} else {
message.error(data.message || '登录失败,请重试')
}
message.success('登录成功!')
// 校验 redirect 防止开放重定向漏洞
const redirect = (route.query.redirect as string) || '/'
const safeRedirect = redirect.startsWith('/') && !redirect.includes('//') ? redirect : '/'
setTimeout(() => {
router.push(safeRedirect)
}, 500)
} catch (error: unknown) {
if (error && typeof error === 'object' && 'errorFields' in error) {
return
}
if (error instanceof ApiError) {
message.error(error.message)
return
}
console.error('登录错误:', error)
message.error('登录失败,请检查网络连接')
} finally {
+25 -24
View File
@@ -2,6 +2,9 @@
import Brand from '@/components/Brand.vue'
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import { api } from '@/utils/request'
import { ApiError } from '@/types/api'
import type { UserInfo } from '@/stores/user'
const formState = reactive({
username: '',
@@ -47,38 +50,36 @@ const handleSubmit = async () => {
await formRef.value.validate()
loading.value = true
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: formState.username,
email: formState.email,
password: formState.password,
}),
const data = await api.post<{
access_token?: string
refresh_token?: string
user?: UserInfo
}>('/api/auth/register', {
username: formState.username,
email: formState.email,
password: formState.password,
})
const data = await response.json()
if (data.code === 0) {
// 注册成功后自动设置 token(如果后端返回)
if (data.data?.access_token) {
userStore.setToken(data.data.access_token, data.data.refresh_token)
if (data.data.user) {
userStore.setUser(data.data.user)
}
// 注册成功后自动设置 token(如果后端返回)
if (data?.access_token) {
userStore.setToken(data.access_token, data.refresh_token!)
if (data.user) {
userStore.setUser(data.user)
}
message.success('注册成功!即将跳转到登录页...')
setTimeout(() => {
router.push('/login')
}, 1500)
} else {
message.error(data.message || '注册失败,请重试')
}
message.success('注册成功!即将跳转到登录页...')
setTimeout(() => {
router.push('/login')
}, 1500)
} catch (error: unknown) {
if (error && typeof error === 'object' && 'errorFields' in error) {
return
}
if (error instanceof ApiError) {
message.error(error.message)
return
}
console.error('注册错误:', error)
message.error('注册失败,请检查网络连接')
} finally {