Python 接入指南

使用通用 OAuth 2.1 库(authlib)接入 UserMatrix,支持 Flask/Django/FastAPI。

UM 目前未提供 Python 专属 SDK,但 UM 是标准 OAuth 2.1 + OIDC 服务,可直接用通用库 authlib 接入。下方示例适用于 Flask / Django / FastAPI。

安装

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 参考