Skip to content

一个框架无关的统一第三方登录 Python SDK,提供简单、一致的多平台 OAuth2 登录集成方案

License

Notifications You must be signed in to change notification settings

tomy128/unilogin-py

Repository files navigation

🔐 unilogin-py

PyPI version Python Support License: MIT Test Status

一个框架无关的统一第三方登录 Python SDK,提供简单、一致的多平台 OAuth2 登录集成方案。

✨ 特性

  • 🔹 框架无关:不依赖特定 Web 框架,可用于 Flask、FastAPI、Django 等
  • 🔹 插件式架构:支持动态扩展新的 Provider
  • 🔹 统一接口:所有平台提供一致的 API 调用方式
  • 🔹 轻量依赖:仅依赖 requestspydantic
  • 🔹 类型安全:完整的 TypeScript 风格类型注解
  • 🔹 生产就绪:包含重试机制、错误处理、日志记录

🚀 支持平台

平台 状态 Provider 名称
GitHub ✅ 已支持 github
微信 ✅ 已支持 wechat
QQ ✅ 已支持 qq
支付宝 🚧 计划中 alipay
Google 🚧 计划中 google

📦 安装

pip install unilogin-py

开发版本:

pip install unilogin-py[dev]

🎯 快速开始

基础用法

from unilogin.factory import get_provider

# 创建 GitHub Provider
provider = get_provider(
    "github",
    client_id="your_github_client_id",
    client_secret="your_github_client_secret",
    redirect_uri="http://localhost:5000/callback/github"
)

# 生成授权链接
auth_url = provider.get_auth_url(state="random_state_string")
print(f"请访问: {auth_url}")

# 处理回调,获取用户信息
code = "从回调 URL 中获取的 code 参数"
token_info = provider.get_access_token(code)
user_info = provider.get_user_info(token_info.access_token)

print(f"用户信息: {user_info.model_dump_json(indent=2)}")

Flask 集成示例

from flask import Flask, redirect, request, jsonify, session
from unilogin.factory import get_provider
from unilogin.core.exceptions import UniLoginError
import secrets

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

# 配置信息
PROVIDERS_CONFIG = {
    "github": {
        "client_id": "your_github_client_id",
        "client_secret": "your_github_client_secret",
        "redirect_uri": "http://localhost:5000/callback/github"
    }
}

@app.route("/login/<provider_name>")
def login(provider_name):
    """发起登录"""
    try:
        config = PROVIDERS_CONFIG.get(provider_name)
        if not config:
            return jsonify({"error": f"不支持的 Provider: {provider_name}"}), 400
        
        provider = get_provider(provider_name, **config)
        state = secrets.token_urlsafe(32)
        session[f"{provider_name}_state"] = state
        
        auth_url = provider.get_auth_url(state=state)
        return redirect(auth_url)
        
    except UniLoginError as e:
        return jsonify({"error": str(e)}), 400

@app.route("/callback/<provider_name>")
def callback(provider_name):
    """处理登录回调"""
    try:
        config = PROVIDERS_CONFIG.get(provider_name)
        provider = get_provider(provider_name, **config)
        
        # 验证 state 参数
        state = request.args.get("state")
        expected_state = session.get(f"{provider_name}_state")
        if state != expected_state:
            return jsonify({"error": "Invalid state parameter"}), 400
        
        code = request.args.get("code")
        if not code:
            return jsonify({"error": "Missing authorization code"}), 400
        
        # 获取访问令牌和用户信息
        token_info = provider.get_access_token(code)
        user_info = provider.get_user_info(token_info.access_token)
        
        return jsonify({
            "message": "登录成功",
            "user": user_info.model_dump(),
            "token": token_info.model_dump()
        })
        
    except UniLoginError as e:
        return jsonify({"error": str(e)}), 400

if __name__ == "__main__":
    app.run(debug=True)

📚 API 文档

核心接口

get_provider(name: str, **kwargs) -> BaseProvider

创建指定的 Provider 实例。

参数:

  • name: Provider 名称(如 "github", "wechat", "qq")
  • client_id: OAuth2 客户端 ID
  • client_secret: OAuth2 客户端密钥
  • redirect_uri: 授权回调地址

BaseProvider 方法

  • get_auth_url(state: str = "") -> str: 生成授权链接
  • get_access_token(code: str) -> TokenInfo: 获取访问令牌
  • get_user_info(access_token: str, **kwargs) -> UserInfo: 获取用户信息

数据模型

UserInfo

class UserInfo(BaseModel):
    id: str                    # 第三方平台用户唯一标识
    nickname: str              # 用户昵称
    avatar: Optional[str]      # 用户头像 URL
    gender: Optional[str]      # 用户性别
    email: Optional[str]       # 用户邮箱
    provider: str              # 平台名称
    raw_data: Optional[dict]   # 原始数据
    created_at: datetime       # 创建时间

TokenInfo

class TokenInfo(BaseModel):
    access_token: str          # 访问令牌
    token_type: str           # 令牌类型
    expires_in: Optional[int] # 过期时间(秒)
    refresh_token: Optional[str] # 刷新令牌
    scope: Optional[str]      # 授权范围
    raw_data: Optional[dict]  # 原始响应数据
    created_at: datetime      # 创建时间

🔧 扩展新 Provider

from unilogin.core.base_provider import BaseProvider
from unilogin.core.models import UserInfo, TokenInfo
from unilogin.factory import register_provider

class CustomProvider(BaseProvider):
    @property
    def provider_name(self) -> str:
        return "custom"
    
    @property
    def auth_url(self) -> str:
        return "https://custom.com/oauth/authorize"
    
    @property
    def token_url(self) -> str:
        return "https://custom.com/oauth/token"
    
    @property
    def user_info_url(self) -> str:
        return "https://custom.com/api/user"
    
    def get_auth_url(self, state: str = "", **kwargs) -> str:
        # 实现授权链接生成逻辑
        pass
    
    def get_access_token(self, code: str, **kwargs) -> TokenInfo:
        # 实现令牌获取逻辑
        pass
    
    def get_user_info(self, access_token: str, **kwargs) -> UserInfo:
        # 实现用户信息获取逻辑
        pass

# 注册新 Provider
register_provider("custom", CustomProvider)

🧪 测试

# 安装开发依赖
pip install -e .[dev]

# 运行测试
pytest

# 运行测试并生成覆盖率报告
pytest --cov=unilogin --cov-report=html

# 代码格式化
black unilogin/
isort unilogin/

# 类型检查
mypy unilogin/

📄 许可证

本项目采用 MIT 许可证

🤝 贡献

欢迎贡献代码!请查看 贡献指南 了解详细信息。

📞 支持

🗺️ 路线图

  • v0.3.0: 新增支付宝 Provider
  • v0.4.0: FastAPI 集成 & 缓存机制
  • v1.0.0: 稳定版发布,PyPI 上架

About

一个框架无关的统一第三方登录 Python SDK,提供简单、一致的多平台 OAuth2 登录集成方案

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published