Python 接入指南

官方 Python SDK(零依赖单文件)或通用 OAuth 2.1 库(authlib)接入 UserMatrix,支持 Flask/Django/FastAPI。

UM 提供官方 Python SDKum_open_sdk.py,单文件、零第三方依赖、纯标准库)。UM 同时是标准 OAuth 2.1 + OIDC 服务,你也可以用通用库 authlib 接入。

方式一:官方 Python SDK(推荐)

获取

# 单文件,直接下载放进项目即可,无需 pip
curl -O https://um.yunjii.cn/php/sdk/um_open_sdk.py

OAuth 2.1 按钮登录

from um_open_sdk import UMOpenClient

um = UMOpenClient('your_appid', 'your_appkey',
                  'https://your-app.com/callback')

# 1) 登录入口:生成 PKCE,跳转授权页
verifier, challenge = um.gen_pkce()
state = um.gen_state()
session['pkce_v'], session['state'] = verifier, state
return redirect(um.authorize_url(state, challenge))

# 2) 回调:授权码换 token + 取用户
tok = um.exchange_code(request.args['code'], session['pkce_v'])
# tok: {'code': 0, 'access_token': 'eyJ...', 'refresh_token': 'rt_...', ...}
user = um.userinfo(tok['access_token'])
# user: {sub, name, nickname, picture, email, login_type, ...}

# 3) 刷新 / 吊销 / 单点登出
new_tok = um.refresh_token(tok['refresh_token'])   # 轮换制
um.revoke(tok['access_token'])
logout_url = um.logout_url('https://your-app.com/')

微信扫码登录(桌面端)

from um_open_sdk import UMOpenQr

qr = UMOpenQr('your_appid', 'your_appkey',
              'https://your-app.com/callback')

# 服务端取二维码(返回 base64 图片 + logid)
r = qr.fetch_qr('wx', size=200)
# r: {'img': 'data:image/jpeg;base64,...', 'logid': 12345}

# 前端每 1.5s 轮询(由你的后端转发调用)
st = qr.poll(r['logid'])
# pending: {'code': 0, 'status': ...}
# 成功:    {'token': 'eyJ...', 'user': {...}}

桌面「微信扫码登录」是 UM 原生机制(公众号带参二维码 + 轮询),无法用纯 OAuth redirect 实现,必须走 UMOpenQr。QR 端点固定使用 https://um.yunjii.cn 根域。

方式二:authlib(通用 OIDC 库)

安装

pip install authlib httpx

OIDC Discovery 配置

authlib 可自动从 OIDC Discovery 端点读取所有配置:

from authlib.integrations.flask_client import OAuth

oauth = OAuth()
oauth.register(
    name='um',
    server_metadata_url='https://um.yunjii.cn/.well-known/openid-configuration',
    client_id='your_appid',
    client_secret='your_appkey',
    client_kwargs={'scope': 'openid profile'},
)

Flask 集成示例

from flask import Flask, session, redirect, url_for, request, jsonify
from authlib.integrations.flask_client import OAuth

app = Flask(__name__)
app.secret_key = 'your-secret-key'

oauth = OAuth(app)
oauth.register(
    name='um',
    server_metadata_url='https://um.yunjii.cn/.well-known/openid-configuration',
    client_id='your_appid',
    client_secret='your_appkey',
    client_kwargs={'scope': 'openid profile'},
)

@app.route('/login')
def login():
    redirect_uri = url_for('callback', _external=True)
    return oauth.um.authorize_redirect(redirect_uri)

@app.route('/callback')
def callback():
    token = oauth.um.authorize_access_token()
    # token 结构:
    # {
    #   'access_token': 'eyJ...',
    #   'token_type': 'Bearer',
    #   'expires_in': 604800,
    #   'refresh_token': 'rt_xxx...',
    #   'user': {'id': 123, 'openid': 'um_xxx', 'nickname': '张三', ...}
    # }

    session['access_token'] = token['access_token']
    session['refresh_token'] = token['refresh_token']
    return redirect('/dashboard')

@app.route('/user')
def user():
    access_token = session.get('access_token')
    if not access_token:
        return redirect('/login')

    # 获取 OIDC UserInfo
    userinfo = oauth.um.userinfo(token={'access_token': access_token})
    return jsonify(userinfo)
    # => {sub, name, nickname, picture, email, login_type, ...}

@app.route('/refresh')
def refresh():
    refresh_token = session.get('refresh_token')
    if not refresh_token:
        return redirect('/login')

    # 刷新 token(轮换制,旧 refresh_token 失效)
    new_token = oauth.um.fetch_access_token(
        grant_type='refresh_token',
        refresh_token=refresh_token,
    )
    session['access_token'] = new_token['access_token']
    session['refresh_token'] = new_token['refresh_token']
    return jsonify({'msg': 'token refreshed'})

Django 集成示例

# views.py
from django.shortcuts import redirect
from django.http import JsonResponse
from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore

from authlib.integrations.django_client import OAuth

oauth = OAuth()
oauth.register(
    name='um',
    server_metadata_url='https://um.yunjii.cn/.well-known/openid-configuration',
    client_id=settings.UM_APPID,
    client_secret=settings.UM_APPKEY,
    client_kwargs={'scope': 'openid profile'},
)

def login(request):
    redirect_uri = settings.UM_CALLBACK_URL
    return oauth.um.authorize_redirect(request, redirect_uri)

def callback(request):
    token = oauth.um.authorize_access_token(request)
    request.session['access_token'] = token['access_token']
    request.session['refresh_token'] = token['refresh_token']
    return redirect('/dashboard')

def user_info(request):
    access_token = request.session.get('access_token')
    if not access_token:
        return redirect('/login')
    userinfo = oauth.um.userinfo(token={'access_token': access_token})
    return JsonResponse(userinfo)

FastAPI 集成示例

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth

app = FastAPI()
oauth = OAuth()
oauth.register(
    name='um',
    server_metadata_url='https://um.yunjii.cn/.well-known/openid-configuration',
    client_id='your_appid',
    client_secret='your_appkey',
    client_kwargs={'scope': 'openid profile'},
)

@app.get('/login')
async def login(request: Request):
    redirect_uri = request.url_for('callback')
    return await oauth.um.authorize_redirect(request, redirect_uri)

@app.get('/callback')
async def callback(request: Request):
    token = await oauth.um.authorize_access_token(request)
    request.session['access_token'] = token['access_token']
    request.session['refresh_token'] = token['refresh_token']
    return RedirectResponse(url='/dashboard')

@app.get('/user')
async def user(request: Request):
    access_token = request.session.get('access_token')
    if not access_token:
        return RedirectResponse(url='/login')
    userinfo = await oauth.um.userinfo(token={'access_token': access_token})
    return userinfo

手动调用(不使用 authlib)

如不想引入 authlib,可用标准 requests 库手动调用 OAuth 2.1 端点:

import requests
import secrets
import hashlib
import base64

UM_BASE = 'https://um.yunjii.cn'
APPID = 'your_appid'
APPKEY = 'your_appkey'
CALLBACK = 'https://your-app.com/callback'

def generate_pkce():
    verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip('=')
    challenge = base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode()).digest()
    ).decode().rstrip('=')
    return verifier, challenge

def exchange_token(code, code_verifier):
    resp = requests.post(f'{UM_BASE}/oauth/token.php', data={
        'grant_type': 'authorization_code',
        'code': code,
        'client_id': APPID,
        'client_secret': APPKEY,
        'redirect_uri': CALLBACK,
        'code_verifier': code_verifier,
    })
    return resp.json()

def get_userinfo(access_token):
    resp = requests.get(f'{UM_BASE}/oauth/userinfo.php', headers={
        'Authorization': f'Bearer {access_token}',
    })
    return resp.json()

def refresh_token(refresh_token):
    resp = requests.post(f'{UM_BASE}/oauth/token.php', data={
        'grant_type': 'refresh_token',
        'refresh_token': refresh_token,
        'client_id': APPID,
        'client_secret': APPKEY,
    })
    return resp.json()

UM OAuth 2.1 端点使用 client_secret_post 鉴权方式,token 响应 code: 0 表示成功。详见 OAuth 2.1 API 参考