提供北京大学统一认证支持,仅需配置环境变量

This commit is contained in:
2026-03-18 08:57:47 +08:00
parent a6ae3b8845
commit b9c83617ad
7 changed files with 502 additions and 147 deletions

View File

@@ -0,0 +1,93 @@
import { NextRequest, NextResponse } from 'next/server'
import { encode } from 'next-auth/jwt'
import { db } from '@/server/db'
import { authOptions, buildUserJwtPayload, userAuthInclude } from '@/server/auth'
import { validateIaaaToken } from '@/server/service/iaaa'
import { clearSessionInvalidation } from '@/server/service/session'
/**
* IAAA 统一认证回调路由
*
* IAAA 认证成功后会 302 重定向到此路由,携带 token 参数。
* 本路由验证 token、查找本地用户、生成 JWT session cookie然后重定向到首页。
*/
export async function GET(req: NextRequest) {
const token = req.nextUrl.searchParams.get('token')
const loginUrl = new URL('/login', req.url)
if (!token) {
loginUrl.searchParams.set('iaaa_error', 'iaaa_no_token')
return NextResponse.redirect(loginUrl)
}
// 获取客户端真实 IP反向代理场景取 X-Forwarded-For 第一个值)
const remoteAddr =
req.headers.get('x-forwarded-for')?.split(',')[0].trim() ||
req.headers.get('x-real-ip') ||
'127.0.0.1'
// 调用 IAAA 验证
const userInfo = await validateIaaaToken(token, remoteAddr)
if (!userInfo) {
loginUrl.searchParams.set('iaaa_error', 'iaaa_validate_failed')
return NextResponse.redirect(loginUrl)
}
// 用 identityId学号/职工号)匹配本地用户
const user = await db.user.findUnique({
where: { id: userInfo.identityId },
include: userAuthInclude,
})
if (!user) {
console.warn(`[IAAA] 用户 ${userInfo.identityId}${userInfo.name})在系统中不存在`)
loginUrl.searchParams.set('iaaa_error', 'iaaa_user_not_found')
return NextResponse.redirect(loginUrl)
}
// 更新最近登录时间
await db.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() },
})
// 清除会话失效标记
await clearSessionInvalidation(user.id)
// 构建 JWT payload与密码登录完全一致
const payload = buildUserJwtPayload(user)
// 生成 next-auth 兼容的 JWT
const secret = authOptions.secret || process.env.NEXTAUTH_SECRET
if (!secret) {
console.error('[IAAA] NEXTAUTH_SECRET 未配置')
loginUrl.searchParams.set('iaaa_error', 'iaaa_server_error')
return NextResponse.redirect(loginUrl)
}
const maxAge = authOptions.session?.maxAge ?? 30 * 24 * 60 * 60
const encodedToken = await encode({
token: { ...payload, sub: payload.id },
secret,
maxAge,
})
// 设置 session cookie 并重定向到首页
// next-auth 在 HTTPS 环境下使用 __Secure- 前缀
const useSecureCookie = req.nextUrl.protocol === 'https:'
const cookieName = useSecureCookie
? '__Secure-next-auth.session-token'
: 'next-auth.session-token'
const response = NextResponse.redirect(new URL('/', req.url))
response.cookies.set(cookieName, encodedToken, {
httpOnly: true,
secure: useSecureCookie,
sameSite: 'lax',
path: '/',
maxAge,
})
console.info(`[IAAA] 登录成功: ${userInfo.identityId}${userInfo.name}`)
return response
}