在 2libra 用户设置页面(/user/setting/oauth)申请新的 OAuth 客户端,填写:
profile)审核通过后,在「我申请的应用」列表中获取 client_secret(仅返回一次,请妥善保存)。
用户 → 第三方应用
第三方应用 → 2libra 授权页面 (/oauth/authorize)
用户确认授权
2libra → 回调第三方应用 (redirect_uri?code=xxx&state=xxx)
第三方应用 → 交换 Access Token (POST /api/oauth/token)
第三方应用 → 获取用户信息 (GET /api/oauth/me)
https://2libra.com/apihttps://2libra.com/oauth/authorize| 端点 | 方法 | 说明 |
|---|---|---|
/oauth/authorize | GET | 授权页面(前端页面) |
/api/oauth/token | POST | 交换 Access Token |
/api/oauth/me | GET | 获取用户信息 |
GET https://2libra.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=profile&state={state}
参数:
client_id: 客户端 ID(必填)redirect_uri: 回调 URI(必填,必须在申请时注册)response_type: 固定为 codescope: 权限范围,v1 仅支持 profilestate: 状态参数,用于防止 CSRF(建议使用随机字符串)用户确认授权后,会重定向到 redirect_uri:
https://example.com/oauth/callback?code=AUTH_CODE&state=xyz123
授权码有效期: 10 分钟,单次使用
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code={AUTH_CODE}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}
响应:
{ "c": 0, "m": "请求成功", "d": { "access_token": "xxx", "token_type": "Bearer", "expires_in": 7200, "scope": "profile" } }
重要: redirect_uri 必须与授权时完全一致
GET /api/oauth/me
Authorization: Bearer {access_token}
响应(profile scope):
{ "c": 0, "m": "请求成功", "d": { "id": "uuid", "username": "jimmy", "user_number": "u_123456", "avatar_url": "https://...", "level": 4 } }
import axios from 'axios'; const API_BASE = 'https://2libra.com/api'; const OAUTH_AUTHORIZE_URL = 'https://2libra.com/oauth/authorize'; const config = { clientId: 'your_client_id', clientSecret: 'your_client_secret', redirectUri: 'https://example.com/oauth/callback', }; // 生成授权 URL function getAuthorizationUrl(state: string): string { const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: config.redirectUri, response_type: 'code', scope: 'profile', state: state, }); return `${OAUTH_AUTHORIZE_URL}?${params.toString()}`; } // 交换 Token async function exchangeCodeForToken(code: string): Promise<string> { const response = await axios.post( `${API_BASE}/oauth/token`, new URLSearchParams({ grant_type: 'authorization_code', code: code, client_id: config.clientId, client_secret: config.clientSecret, redirect_uri: config.redirectUri, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, } ); return response.data.d.access_token; } // 获取用户信息 async function getUserInfo(accessToken: string) { const response = await axios.get(`${API_BASE}/oauth/me`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); return response.data.d; } // Express 路由 app.get('/oauth/callback', async (req, res) => { const { code, state, error } = req.query; if (error) { return res.status(400).send(`Authorization failed: ${error}`); } try { const accessToken = await exchangeCodeForToken(code as string); const userInfo = await getUserInfo(accessToken); // 保存用户信息 req.session.user = userInfo; req.session.accessToken = accessToken; res.redirect('/dashboard'); } catch (error) { console.error('OAuth error:', error); res.status(500).send('OAuth flow failed'); } });
import requests from flask import Flask, redirect, request, session import secrets app = Flask(__name__) app.secret_key = 'your-secret-key' API_BASE = 'https://2libra.com/api' OAUTH_AUTHORIZE_URL = 'https://2libra.com/oauth/authorize' CLIENT_ID = 'your_client_id' CLIENT_SECRET = 'your_client_secret' REDIRECT_URI = 'https://example.com/oauth/callback' @app.route('/oauth/authorize') def authorize(): state = secrets.token_urlsafe(32) session['oauth_state'] = state params = { 'client_id': CLIENT_ID, 'redirect_uri': REDIRECT_URI, 'response_type': 'code', 'scope': 'profile', 'state': state, } url = f"{OAUTH_AUTHORIZE_URL}?{requests.compat.urlencode(params)}" return redirect(url) @app.route('/oauth/callback') def callback(): if request.args.get('state') != session.get('oauth_state'): return 'Invalid state', 400 if 'error' in request.args: return f"Authorization failed: {request.args.get('error')}", 400 code = request.args.get('code') # 交换 Token token_response = requests.post( f'{API_BASE}/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'redirect_uri': REDIRECT_URI, }, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) if token_response.status_code != 200: return 'Failed to exchange token', 500 access_token = token_response.json()['d']['access_token'] # 获取用户信息 user_response = requests.get( f'{API_BASE}/oauth/me', headers={'Authorization': f'Bearer {access_token}'}, ) user_info = user_response.json()['d'] session['user'] = user_info session['access_token'] = access_token return redirect('/dashboard')
redirect_uri 必须与交换 token 时完全一致| 错误信息 | 原因 |
|---|---|
无效或已过期的授权码 | 授权码已过期、已使用或 redirect_uri 不匹配 |
无效的客户端 ID | client_id 不存在或未激活 |
无效的客户端密钥 | client_secret 不正确 |
无效的重定向 URI | redirect_uri 不在注册列表中 |
更新时间:2026-01-27 15:46:21