个人资质实现微信授权登录和内嵌微信二维码扫码登录

微信授权登录和微信扫码登录代码详细分析

一、整体架构

1.1 技术栈

  • 后端 : Flask + JWT + YunGouOS API
  • 前端 : uni-app (Vue3) + 微信JS SDK (wxLogin.js)
  • 第三方服务 : YunGouOS (微信开放平台接口封装)

1.2 登录方式对比

二、数据模型

2.1 WechatUser 模型

python 复制代码
class WechatUser(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    openid = db.Column(db.String(128), unique=True, 
    nullable=False)  # 微信唯一标识
    unionid = db.Column(db.String(128))  # 开放平台统一标识
    nickname = db.Column(db.String(128))  # 昵称
    avatar_url = db.Column(db.String(512))  # 头像
    gender = db.Column(db.Integer, default=0)  # 性别
    country = db.Column(db.String(64))  # 国家
    province = db.Column(db.String(64))  # 省份
    city = db.Column(db.String(64))  # 城市
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))  # 关
    联系统用户

2.2 扫码会话存储

python 复制代码
# 内存存储扫码登录状态(生产环境建议使用Redis)
qrcode_login_sessions = {}

# 会话结构
{
    'scene_id': 'weblogin_1777994028_xxx',  # 场景ID
    'status': 'waiting',  # waiting/confirmed/expired
    'openid': None,  # 微信openid
    'user_id': None,  # 系统用户ID
    'token': None,  # JWT token
    'username': None,  # 用户名
    'created_at': 1777994028,  # 创建时间
    'expires_at': 1777994328  # 过期时间(5分钟)
}

三、YunGouOS 签名算法

3.1 签名生成

python 复制代码
def create_sign(params, partner_key):
    # 1. 复制参数,移除sign
    sign_params = params.copy()
    sign_params.pop('sign', None)
    
    # 2. 过滤空值参数
    filtered_params = {k: v for k, v in sign_params.items() if v 
    is not None and v != ''}
    
    # 3. 按参数名ASCII码升序排序
    sorted_params = sorted(filtered_params.items())
    
    # 4. 拼接字符串:key1=value1&key2=value2...
    string_a = '&'.join([f"{k}={v}" for k, v in sorted_params])
    
    # 5. 拼接密钥
    string_sign_temp = f"{string_a}&key={partner_key}"
    
    # 6. MD5加密并转大写
    sign = hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest
    ().upper()
    
    return sign

3.2 签名规则

  • 只有必填参数参与签名
  • 可选参数(如 params )不参与签名
  • 参数按 ASCII 码升序排序
  • 最后拼接 &key=partner_key
  • MD5 加密后转大写

四、微信授权登录 (H5)

4.1 后端接口

1. 获取授权链接

接口 : GET /api/wechat/h5/auth-url

功能 :

  1. 调用 YunGouOS get_oauth_url() 获取授权链接
  2. 授权类型: mp-base (基础授权,静默获取openid)
    返回数据 :
json 复制代码
{
    "msg": "获取授权链接成功",
    "auth_url": "https://open.weixin.qq.com/connect/oauth2/authorize?..."
}
2.授权回调

接口 : GET /api/wechat/oauth/callback

功能 :

  1. 接收微信回调参数 code
  2. 调用 YunGouOS get_oauth_info() 查询授权信息
  3. 解析 openid
  4. 查找或创建 WechatUser
  5. 检查是否已绑定系统账号
  6. 已绑定:生成JWT token,重定向到前端
  7. 未绑定:重定向到绑定页面
    重定向URL :
3.H5登录接口

接口 : POST /api/wechat/h5/login

功能 :

  1. 接收前端传来的 code
  2. 调用 YunGouOS get_oauth_info() 查询授权信息
  3. 查找或创建 WechatUser
  4. 检查是否已绑定系统账号
  5. 返回 JSON 响应
    返回数据 :
json 复制代码
// 已绑定
{
    "msg": "登录成功",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
        "id": 1,
        "username": "admin",
        "avatar": "..."
    }
}

// 未绑定
{
    "msg": "未绑定系统账号",
    "openid": "o-_-itxxxxxxxxxxxxxx",
    "need_bind": true
}

4.2 前端实现

文件 : uniapp/pages/auth/wechat-login.vue

核心流程

vue 复制代码
onMounted(async () => {
    // 1. 检测是否在微信浏览器中
    // 检测是否在微信浏览器中
    // if (!isWechatBrowser()) {
    //     status.value = 'error';
    //     errorMessage.value = '请在微信客户端中打开此页面进行授权登录';
    //     return;
    // }
    
    // 2. 检查是否有回调参数(从后端重定向回来)
    if (hasCallbackParams) {
        handleCallback();  // 处理token/openid/error参数
    } else {
        // 3. 检查是否有code参数(微信授权回调)
        if (code) {
            handleWechatLogin(code);  // 调用后端登录
        } else {
            // 4. 没有code,跳转到微信授权
            const authUrl = await wechatApi.getAuthUrl();
            window.location.href = authUrl;
        }
    }
});

状态处理

Token 保存

js 复制代码
// 使用 userStore 同步状态
userStore.token = res.token;
userStore.userInfo = res.user;

// 持久化到本地存储
setToken(res.token);
setUserInfo(res.user);

五、微信扫码登录 (PC)

5.1 后端接口

初始化扫码登录

接口 : POST /api/wechat/weblogin/init

功能 :

  1. 生成唯一 scene_id
  2. 创建会话并存储到 qrcode_login_sessions
  3. 调用 YunGouOS get_web_login() 获取扫码参数
  4. 构建微信开放平台扫码授权URL
  5. 返回参数给前端
    返回数据 :
json 复制代码
{
    "msg": "获取成功",
    "scene_id": "weblogin_1777994028_xxx",
    "auth_url": "https://open.weixin.qq.com/connect/qrconnect?...",
    "appId": "wx7xxxxxxxxxxxx",
    "scope": "snsapi_login",
    "state": "B4C2B73xxxxxxxxxxxxxxx6AA8B",
    "redirect_uri": "https://api.wx.yungouos.com/callback/wxmp/oauth",
    "expires_in": 300
}
扫码登录回调

接口 : GET /api/wechat/weblogin/callback

功能 :

  1. 接收微信回调参数 code 和 state
  2. 调用 YunGouOS get_oauth_info() 查询授权信息
  3. 解析 scene_id 和 openid
  4. 查找会话并检查是否过期
  5. 查找或创建 WechatUser
  6. 检查是否已绑定系统账号
  7. 如果已绑定:更新会话状态为 confirmed ,生成JWT token
  8. 返回简单HTML页面(避免iframe显示JSON)
    返回内容 :
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>扫码成功</title>
</head>
<body>
    <div class="container">
        <div class="icon">✓</div>
        <h1>扫码成功</h1>
        <p>请在原页面查看登录状态</p>
    </div>
</body>
</html>
扫码状态轮询

接口 : GET /api/wechat/weblogin/status?scene_id=xxx

功能 :

  1. 根据 scene_id 查询会话状态
  2. 检查是否过期
  3. 返回当前状态和登录信息
    返回数据 :
json 复制代码
{
    "status": "confirmed",  // waiting/confirmed/expired
    "msg": "登录成功",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
        "id": 1,
        "username": "admin"
    }
}
微信账号绑定

接口 : POST /api/wechat/bind

功能 :

  1. 验证系统账号密码
  2. 查找微信用户
  3. 绑定微信到系统账号(设置 wechat_user.user_id )
  4. 生成JWT token并返回

5.2 前端实现

文件 : uniapp/pages/auth/qrcode-login.vue

核心流程

js 复制代码
// 动态加载微信JS文件
const loadWxLoginScript = () => {
  return new Promise((resolve, reject) => {
    if (typeof WxLogin !== "undefined") {
      resolve();
      return;
    }

    const script = document.createElement("script");
    script.src =
      "https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
    script.onload = () => resolve();
    script.onerror = () => reject(new Error("加载微信JS文件失败"));
    document.head.appendChild(script);
  });
};

生成二维码

js 复制代码
const generateQrcode = async () => {
    // 1. 调用后端获取扫码参数
    const res = await wechatApi.initWebLogin();
    
    // 2. 动态加载微信JS SDK
    if (typeof WxLogin === 'undefined') {
        await loadWxLoginScript();
    }
    
    // 3. 设置状态渲染容器
    status.value = 'waiting';
    await nextTick();
    
    // 4. 初始化微信二维码
    new WxLogin({
        self_redirect: true,  // 在当前iframe内跳转
        id: 'wx_qrcode_container',
        appid: res.appId,
        scope: res.scope,
        redirect_uri: encodeURIComponent(res.redirect_uri),
        state: res.state,
        style: 'black',
        href: '',
        fast_login: 0,
    });
    
    // 5. 开始轮询和倒计时
    startPolling(res.scene_id);
    startCountdown(res.expires_in);
};

状态轮询

js 复制代码
const startPolling = (sid) => {
    pollTimer = setInterval(async () => {
        const res = await wechatApi.checkWebloginStatus({ scene_id: sid });
        
        if (res.status === 'confirmed') {
            // 登录成功,保存token并跳转
            userStore.token = res.token;
            userStore.userInfo = res.user;
            setToken(res.token);
            setUserInfo(userStore.userInfo);
            
            setTimeout(() => {
                window.location.href = '/#/pages/index/index';
            }, 1500);
        } else if (res.status === 'expired') {
            // 二维码过期
            status.value = 'expired';
            clearInterval(pollTimer);
        }
    }, 3000);
};

倒计时

js 复制代码
const startCountdown = (seconds) => {
  countdown.value = seconds;

  if (countdownTimer) {
    clearInterval(countdownTimer);
  }

  countdownTimer = setInterval(() => {
    countdown.value--;
    if (countdown.value <= 0) {
      clearInterval(countdownTimer);
    }
  }, 1000);
};

状态流转

PlainText 复制代码
loading → waiting → (iframe显示扫码成功) → confirmed → success → 跳转首页
                     ↓
                   expired → 显示刷新按钮

六、YunGouOS API 调用

6.1 获取授权链接

python 复制代码
def get_oauth_url(mch_id, callback_url, partner_key, auth_type='mp-base', params=None):
    # 只有必填参数参与签名
    sign_params = {
        'mch_id': mch_id,
        'callback_url': callback_url
    }
    
    sign = create_sign(sign_params, partner_key)
    
    # 请求参数包含所有参数
    request_params = {
        'mch_id': mch_id,
        'callback_url': callback_url,
        'type': auth_type,
        'sign': sign
    }
    
    if params:
        request_params['params'] = params
    
    response = requests.post(
        'https://api.wx.yungouos.com/api/wx/getOauthUrl',
        data=request_params,
        timeout=10
    )
    
    return response.json()

6.2 查询授权信息

python 复制代码
def get_oauth_info(mch_id, code, partner_key):
    # 必填参数参与签名
    sign_params = {
        'mch_id': mch_id,
        'code': code
    }
    
    sign = create_sign(sign_params, partner_key)
    
    request_params = {
        'mch_id': mch_id,
        'code': code,
        'sign': sign
    }
    
    response = requests.get(
        'https://api.wx.yungouos.com/api/wx/
        getOauthInfo',
        params=request_params,
        timeout=10
    )
    
    return response.json()

6.3 获取扫码登录参数

python 复制代码
def get_web_login(mch_id, callback_url, partner_key, 
params=None):
    # 只有必填参数参与签名
    sign_params = {
        'mch_id': mch_id,
        'callback_url': callback_url
    }
    
    sign = create_sign(sign_params, partner_key)
    
    request_params = {
        'mch_id': mch_id,
        'callback_url': callback_url,
        'sign': sign
    }
    
    if params:
        request_params['params'] = params
    
    response = requests.post(
        'https://api.wx.yungouos.com/api/wx/
        getWebLogin',
        data=request_params,
        timeout=10
    )
    
    return response.json()

七、配置参数

7.1 环境变量

python 复制代码
# 后端配置
YUNGOUOS_MCH_ID = '100xxxxxx'  # 商户号
YUNGOUOS_PARTNER_KEY = '你的密钥'  # 商户密钥
WECHAT_OAUTH_CALLBACK = 'http://localhost:5555/api/wechat/oauth/callback'
WECHAT_WEB_LOGIN_CALLBACK = 'https://api.wx.yungouos.com/callback/wxmp/oauth'
WECHAT_QRCODE_EXPIRE_TIME = 300  # 5分钟
FRONTEND_URL = 'http://localhost:5173'

八、安全机制

8.1 会话安全

  • 会话存储在内存(生产环境建议用Redis)
  • 5分钟自动过期
  • 每次扫码生成唯一 scene_id

8.2 签名安全

  • YunGouOS 签名使用 MD5 + partner_key
  • 参数按key排序后拼接
  • 防止参数篡改

8.3 Token安全

  • 使用 JWT (flask_jwt_extended)
  • Token 包含用户ID
  • 前端存储在 localStorage 和 userStore

九、流程图

9.1 微信授权登录流程

复制代码
用户访问微信登录页面
        ↓
检测是否在微信浏览器中
        ↓
调用 /api/wechat/h5/auth-url 获取授权链接
        ↓
跳转到微信授权页面 
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6xxxxxxxxxb4a&redirect_uri=https://api.wx.yungouos.com/callback/wxmp/oauth&response_type=code&scope=snsapi_base&state=B695C4xxxxxxxxxx4140E6F2&connect_redirect=1#wechat_redirect
        ↓
用户授权,微信回调 YunGouOS
        ↓
YunGouOS 回调后端 /api/wechat/oauth/callback
http://localhost:5555/api/wechat/oauth/callback?code=B695Cxxxxxxxxxxxxxxx3FD4140E6F2
        ↓
后端查询授权信息,生成JWT token
        ↓
重定向到前端 wechat-login.vue
http://localhost:5173/#/pages/auth/wechat-login?token=eyJ0eXAxxxxxxxxxxxxxxx&user_id=1&username=admin
        ↓
前端处理URL参数,保存token
        ↓
跳转到首页

带token和用户信息重定向到前端,前端解析url保存token、用户信息


跳转到首页

9.2 微信扫码登录流程

复制代码
用户访问扫码登录页面
        ↓
前端调用 /api/wechat/weblogin/init
        ↓
后端生成scene_id,调用YunGouOS获取参数
        ↓
前端加载微信JS SDK,渲染二维码
        ↓
用户用微信扫描二维码
        ↓
微信跳转到 YunGouOS 回调地址
        ↓
YunGouOS 回调后端 /api/wechat/weblogin/callback
        ↓
后端查询授权信息,更新会话状态
        ↓
后端返回HTML页面(iframe显示)
        ↓
前端轮询检测到 confirmed 状态
        ↓
保存token,跳转到首页

十、待优化项

  1. 会话存储 : 当前使用内存,生产环境应改用 Redis
  2. 错误处理 : 增加更详细的错误日志和用户提示
  3. 二维码刷新 : 支持手动刷新和自动刷新
  4. 多端同步 : 支持手机扫码后PC端自动登录
  5. 安全加固 : 增加IP限制、频率限制等
  6. 日志完善 : 统一日志格式,增加请求追踪ID
相关推荐
我叫黑大帅2 分钟前
PyScript-GitHubRepo: 构建高性能GitHub仓库批量下载工具的技术实践
后端·python·面试
lbb 小魔仙16 分钟前
基于Python构建RAG(检索增强生成)系统:从原理到企业级实战
开发语言·python
SunnyDays101134 分钟前
Python 如何精准统计 Word 文档的页数、字数、行数
python·word文档字数统计
小陈的进阶之路1 小时前
Python系列课(2)——判断
java·前端·python
脉动数据行情1 小时前
Python 实现融通金行情数据对接(实时推送 + K 线 + 产品列表)
开发语言·python
wltx16882 小时前
谷歌SEO如何做插床优化?
大数据·人工智能·python
2301_781571422 小时前
JavaScript中Object-getOwnPropertySymbols获取方法
jvm·数据库·python
倒霉熊dd3 小时前
Python学习(第一部分 语法与数据结构/核心基础)
大数据·python·学习·pip
当时只道寻常3 小时前
从零到一打造企业级全栈后台管理系统 —— 技术选型、工程化实践与深度思考
前端·全栈·前端工程化
仅此,3 小时前
deep agent整合 DeepSeek 记录
python·langchain·agent·deep agent sdk