2libra OAuth 接入文档

快速开始

1. 申请 OAuth 客户端

在 2libra 用户设置页面(/user/setting/oauth)申请新的 OAuth 客户端,填写:

  • 应用名称
  • 应用描述(可选)
  • 重定向 URI 列表(至少一个,必须是 http/https URL)
  • 权限范围(v1 仅支持 profile

审核通过后,在「我申请的应用」列表中获取 client_secret(仅返回一次,请妥善保存)。

2. 授权流程

复制
用户 → 第三方应用
第三方应用 → 2libra 授权页面 (/oauth/authorize)
用户确认授权
2libra → 回调第三方应用 (redirect_uri?code=xxx&state=xxx)
第三方应用 → 交换 Access Token (POST /api/oauth/token)
第三方应用 → 获取用户信息 (GET /api/oauth/me)

API 端点

基础 URL

  • 生产环境: https://2libra.com/api
  • 授权页面: https://2libra.com/oauth/authorize

端点列表

端点方法说明
/oauth/authorizeGET授权页面(前端页面)
/api/oauth/tokenPOST交换 Access Token
/api/oauth/meGET获取用户信息

授权流程

Step 1: 跳转授权页面

复制
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: 固定为 code
  • scope: 权限范围,v1 仅支持 profile
  • state: 状态参数,用于防止 CSRF(建议使用随机字符串)

Step 2: 接收授权码

用户确认授权后,会重定向到 redirect_uri

复制
https://example.com/oauth/callback?code=AUTH_CODE&state=xyz123

授权码有效期: 10 分钟,单次使用

Step 3: 交换 Access Token

复制
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 必须与授权时完全一致

Step 4: 获取用户信息

复制
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
  }
}

示例代码

Node.js (Express)

复制
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');
  }
});

Python (Flask)

复制
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')

注意事项

  1. redirect_uri 必须完全匹配:授权时使用的 redirect_uri 必须与交换 token 时完全一致
  2. 授权码单次使用:授权码只能使用一次,使用后立即失效
  3. 授权码有效期:10 分钟,过期后需要重新授权
  4. Access Token 有效期:2 小时,过期后需要重新授权
  5. client_secret 保护:仅在审批通过时返回一次,请妥善保存,不要泄露

常见错误

错误信息原因
无效或已过期的授权码授权码已过期、已使用或 redirect_uri 不匹配
无效的客户端 IDclient_id 不存在或未激活
无效的客户端密钥client_secret 不正确
无效的重定向 URIredirect_uri 不在注册列表中

更新时间:2026-01-27 15:46:21