85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
export default defineEventHandler((event) => {
|
|
const pathname = getRequestURL(event).pathname
|
|
|
|
// 只处理特定的内部API路由,防止站外调用
|
|
const isProtectedApi = pathname.startsWith('/api/api-enhanced/netease') ||
|
|
pathname.startsWith('/api/native-api')
|
|
|
|
if (!isProtectedApi) {
|
|
return
|
|
}
|
|
|
|
// CORS 限制:禁止站外网站调用
|
|
const origin = getHeader(event, 'origin')
|
|
const referer = getHeader(event, 'referer')
|
|
const secFetchSite = getHeader(event, 'sec-fetch-site')
|
|
const sourceUrl = origin || referer
|
|
|
|
if (sourceUrl) {
|
|
try {
|
|
const url = new URL(sourceUrl)
|
|
|
|
const extractHostname = (value: string) => {
|
|
const normalizedValue = value.includes('://') ? value : `http://${value}`
|
|
return new URL(normalizedValue).hostname
|
|
}
|
|
|
|
// 获取当前请求的 hostname,优先使用配置中可信的主机名,防止 x-forwarded-host 伪造攻击
|
|
const config = useRuntimeConfig(event)
|
|
const configuredHost = config.public?.host
|
|
|
|
let requestHostname: string
|
|
if (configuredHost) {
|
|
requestHostname = extractHostname(configuredHost)
|
|
} else {
|
|
// 只有在未配置公网主机名时,才回退到 host 请求头
|
|
const hostHeader = getHeader(event, 'host')
|
|
if (hostHeader) {
|
|
requestHostname = extractHostname(hostHeader)
|
|
} else {
|
|
// 缺少 Host 请求头,这是一个异常情况,应该拒绝请求
|
|
throw createError({ statusCode: 400, message: 'Bad Request: 缺少Host请求头' })
|
|
}
|
|
}
|
|
|
|
const isLocalhost = (h: string) => h === 'localhost' || h === '127.0.0.1' || h === '[::1]'
|
|
|
|
// 如果来源域与当前域不一致,则拒绝请求
|
|
// 注意:允许 localhost 和 127.0.0.1 之间的互访
|
|
if (url.hostname !== requestHostname && !(isLocalhost(url.hostname) && isLocalhost(requestHostname))) {
|
|
console.warn(`[CORS Middleware] 拦截跨域请求: 来源 ${url.hostname}, 期望 ${requestHostname}, 路径 ${pathname}`)
|
|
throw createError({
|
|
statusCode: 403,
|
|
message: 'Forbidden: 内部API不允许跨域请求'
|
|
})
|
|
}
|
|
} catch (e: unknown) {
|
|
// 如果是我们主动抛出的 HTTP 错误,则重新抛出
|
|
if (
|
|
typeof e === 'object' &&
|
|
e !== null &&
|
|
'statusCode' in e &&
|
|
typeof e.statusCode === 'number'
|
|
) {
|
|
throw e
|
|
}
|
|
// 对于其他错误(如无效的URL),这是一个错误的请求
|
|
console.warn(`[CORS Middleware] 无效的来源 URL: ${sourceUrl}`, e)
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'Bad Request: Origin或Referer头无效'
|
|
})
|
|
}
|
|
} else if (secFetchSite === 'same-origin') {
|
|
// 允许没有 Origin/Referer 但明确标记为同源的请求
|
|
return
|
|
} else {
|
|
// 拦截没有来源信息的受保护API请求
|
|
console.warn(`[CORS Middleware] 拦截无Origin/Referer头的请求: 路径 ${pathname}, sec-fetch-site: ${secFetchSite || 'none'}`)
|
|
throw createError({
|
|
statusCode: 403,
|
|
message: 'Forbidden: 访问此API必须提供Origin或Referer头'
|
|
})
|
|
}
|
|
})
|