提供北京大学统一认证支持,仅需配置环境变量
This commit is contained in:
93
src/app/api/auth/iaaa/callback/route.ts
Normal file
93
src/app/api/auth/iaaa/callback/route.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user