OAuth 对接指南
让你的应用支持”用知新芸账号登录”。本页面向开发者,含完整代码示例。如果你只想了解 OAuth 是什么、有哪些能力,请先看 认证 → OAuth 开放。
接入前提
- 联系知新芸管理员,在 认证管理后台 注册一个 OAuth 应用,获得:
client_idclient_secret(只展示一次,妥善保存)- 已配置的回调地址(必须与下面代码中的
redirect_uri完全一致)
- 准备一个能接收 GET 请求的回调端点(例如
https://your-app.com/api/auth/callback/zxyun)
生产环境的 redirect_uri 必须是 HTTPS。本地开发可用 http://localhost:xxxx,但要在管理后台单独添加。
端点速查
| 用途 | 方法 | 端点 |
|---|---|---|
| 授权页(用户跳转) | GET | https://auth.z-xin.net/oauth2/authorize |
| Token 交换 | POST | https://auth.z-xin.net/api/oauth2/token |
| 用户信息 | GET | https://auth.z-xin.net/api/oauth2/userinfo |
| Swagger 全量 API | — | https://auth.z-xin.net/api-docs |
授权码流程(标准 4 步)
1. 把用户跳到知新芸授权页
https://auth.z-xin.net/oauth2/authorize
?client_id=<你的 client_id>
&redirect_uri=<你的回调地址,URL 编码>
&response_type=code
&scope=profile
&state=<随机字符串,防 CSRF>用户在知新芸登录并同意授权后,会带着 code 跳回你的 redirect_uri:
https://your-app.com/api/auth/callback/zxyun?code=xxx&state=xxx2. 验证 state、用 code 换 access_token
⚠️ 这一步必须在后端完成,不要在前端暴露 client_secret。
curl -X POST https://auth.z-xin.net/api/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<上一步的 code>" \
-d "redirect_uri=<同样的回调地址>" \
-d "client_id=<你的 client_id>" \
-d "client_secret=<你的 client_secret>"返回:
{
"access_token": "xxx",
"refresh_token": "xxx",
"token_type": "Bearer",
"expires_in": 604800,
"scope": "profile"
}3. 用 access_token 拉用户信息
curl https://auth.z-xin.net/api/oauth2/userinfo \
-H "Authorization: Bearer <access_token>"返回:
{
"id": "123456789",
"username": "lisi",
"name": "李四",
"email": "lisi@example.com",
"school": "xxx 大学",
"roles": ["teacher"]
}4. 在你的系统建立会话
把用户信息写入你自己的 session / JWT,OAuth 流程结束。
Next.js (App Router) 示例
// app/api/auth/callback/zxyun/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { cookies } from 'next/headers'
export async function GET(req: NextRequest) {
const code = req.nextUrl.searchParams.get('code')
const state = req.nextUrl.searchParams.get('state')
const cookieStore = await cookies()
const savedState = cookieStore.get('oauth_state')?.value
if (!code || state !== savedState) {
return NextResponse.redirect(new URL('/login?error=invalid', req.url))
}
// 换 token
const tokenRes = await fetch('https://auth.z-xin.net/api/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: process.env.OAUTH_REDIRECT_URI!,
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET!,
}),
})
const { access_token } = await tokenRes.json()
// 拉用户信息
const userRes = await fetch('https://auth.z-xin.net/api/oauth2/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
})
const user = await userRes.json()
// 写入你自己的 session(示例用 cookie)
cookieStore.set('app_session', JSON.stringify({ id: user.id, name: user.name }), {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60,
})
return NextResponse.redirect(new URL('/', req.url))
}Node.js (Express) 示例
import express from 'express'
import fetch from 'node-fetch'
const app = express()
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query
if (state !== req.session.oauthState) return res.status(400).send('Invalid state')
const tokenRes = await fetch('https://auth.z-xin.net/api/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: process.env.OAUTH_REDIRECT_URI,
client_id: process.env.OAUTH_CLIENT_ID,
client_secret: process.env.OAUTH_CLIENT_SECRET,
}),
})
const { access_token } = await tokenRes.json()
const user = await fetch('https://auth.z-xin.net/api/oauth2/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
}).then(r => r.json())
req.session.user = user
res.redirect('/')
})Python (Flask) 示例
import os
import requests
from flask import Flask, request, session, redirect
app = Flask(__name__)
@app.route('/auth/callback')
def callback():
code = request.args.get('code')
state = request.args.get('state')
if state != session.get('oauth_state'):
return 'Invalid state', 400
token = requests.post(
'https://auth.z-xin.net/api/oauth2/token',
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': os.environ['OAUTH_REDIRECT_URI'],
'client_id': os.environ['OAUTH_CLIENT_ID'],
'client_secret': os.environ['OAUTH_CLIENT_SECRET'],
},
).json()['access_token']
user = requests.get(
'https://auth.z-xin.net/api/oauth2/userinfo',
headers={'Authorization': f'Bearer {token}'},
).json()
session['user'] = user
return redirect('/')Token 刷新
access_token 默认 7 天有效。临过期前用 refresh_token 续期:
curl -X POST https://auth.z-xin.net/api/oauth2/token \
-d "grant_type=refresh_token" \
-d "refresh_token=<上次拿到的 refresh_token>" \
-d "client_id=<client_id>" \
-d "client_secret=<client_secret>"常见问题
报错 invalid_redirect_uri
回调地址必须与管理后台注册的完全一致(含端口、路径、尾斜杠)。
报错 invalid_client
检查 client_secret 是否泄露后被管理员重置。
用户信息字段缺少 email / school 申请应用时未勾选对应 scope,或对应字段在用户档案中为空。
Last updated on