简介
本技术文档围绕 DouPHP 的“微信登录”插件,系统阐述微信公众平台 OAuth2.0 授权流程在本项目中的落地实现。重点覆盖公众号配置、AppID/AppSecret 设置、授权码获取、用户信息拉取、OpenID/UnionID 映射、头像与昵称处理等关键环节;并给出网页授权登录、小程序登录、APP 登录等不同场景的实现方案与注意事项。同时说明安全机制(state 校验、token 刷新策略、隐私保护)以及 UnionID 多端统一账号的高级能力。
项目结构
微信登录相关代码主要分布在以下位置:
- 插件入口与业务服务:plugin/wxlogin
- 第三方登录通用协调服务:_'/module/user/core/service/user/SnsLoginService.php
- 小程序 API 登录控制器:api/controller/user/WeixinController.php
- 基础配置:config/config.php
graph TB
subgraph "插件层"
P["WxloginProvider<br/>插件接入点"]
S["WxloginService<br/>OAuth2.0 流程"]
end
subgraph "核心服务层"
C["SnsLoginService<br/>统一登录协调"]
end
subgraph "小程序API层"
A["WeixinController<br/>jscode2session / 绑定"]
end
subgraph "配置"
CFG["config.php<br/>应用密钥等"]
end
P --> S
S --> C
A --> C
CFG -.-> S
CFG -.-> A
核心组件
- WxloginProvider:插件对外暴露的接入点,负责声明插件元数据(名称、描述、版本、分组、客户端类型、配置项),并将 start/finish 回调委托给 WxloginService。
- WxloginService:实现微信公众号/开放平台两套 AppID 的自动选择、生成 state、构造授权 URL、回调校验 state、换取 access_token 与 openid、拉取用户信息、组装 SNS 数据并交由 SnsLoginService 完成登录/绑定/注册逻辑。
- SnsLoginService:统一的第三方登录协调器,按优先级处理:已关联用户直接登录、当前登录用户绑定、未登录且允许免绑时自动注册、否则进入绑定流程。
- WeixinController(小程序API):小程序侧通过 jscode2session 获取 openid/session_key,结合 unionid/openid 进行用户查找或绑定,支持手机号绑定与自动注册。
架构总览
下图展示了网页端(公众号/开放平台)与小程序端的整体登录链路。
sequenceDiagram
participant U as "用户浏览器/小程序"
participant P as "WxloginProvider"
participant S as "WxloginService"
participant WX as "微信服务器"
participant C as "SnsLoginService"
Note over U,P : 网页授权登录PC扫码/公众号内
U->>P : 触发开始
P->>S : start()
S->>S : resolveAppCredentials()<br/>生成state并写入Session
S-->>U : 重定向到微信授权页
U->>WX : 授权并回调
WX-->>S : finish(code, state)
S->>S : 校验state
S->>WX : 换取access_token/openid
WX-->>S : token信息
S->>WX : 拉取用户信息
WX-->>S : 用户资料
S->>C : resolve(sns, userProfile, loginIdMode, nobind)
C-->>U : 跳转用户中心/绑定页面
Note over U,C : 小程序登录API
U->>U : wx.login() 获取 code
U->>C : 调用 api/user/weixin/login(code, phone?)
C->>WX : jscode2session
WX-->>C : openid/unionid/session_key
C->>C : 查找/绑定/注册
C-->>U : 返回登录态/绑定结果
详细组件分析
WxloginProvider(插件接入点)
- 职责
- 声明插件标识、名称、描述、版本、分组、支持的客户端类型。
- 提供配置项:开放平台 AppID/AppSecret、公众号 AppID/AppSecret。
- 将 start/finish 请求委派给 WxloginService。
- 关键点
- pluginId 固定为 'wxlogin'。
- meta 中定义四组配置字段,用于后台管理界面展示与保存。
- start/finish 透传参数至服务层,保持插件薄封装。
WxloginService(OAuth2.0 流程)
- 职责
- 根据 UA 判断是公众号还是开放平台,分别使用对应 AppID/AppSecret。
- 生成随机 state 并写入 Session,构建微信授权链接。
- 回调时校验 state,换取 access_token 与 openid,拉取用户信息。
- 组装 sns 数据(group/apptype/openid/unionid/nickname/avatar/sex),交给 SnsLoginService 处理登录/绑定/注册。
- 关键流程
- 授权开始:start()
- 解析凭据:resolveAppCredentials()
- 生成 state:md5(uniqid(mt_rand())) 存入 Session
- 构造回调地址:index.php?route=plugin/wxlogin/finish
- 根据 isMp 决定 scope 与授权 URL(snsapi_userinfo vs snsapi_login)
- 回调处理:finish()
- 校验 state:Session 中期望值与回调 state 一致
- 换取 token:fetchTokenOpenid()
- 拉取用户信息:fetchUserinfo()
- 组装 sns:nickname 非法字符过滤、性别标准化、头像直链
- 决策登录:resolve(loginIdMode='openid'|'unionid', nobind)
- 授权开始:start()
- 错误处理
- 配置缺失、state 不匹配、无法获取 token/用户信息均抛出领域异常并引导回用户页。
flowchart TD
Start(["开始"]) --> Resolve["解析AppID/Secret<br/>按UA选择公众号或开放平台"]
Resolve --> GenState["生成state并写入Session"]
GenState --> BuildURL["构造微信授权URL"]
BuildURL --> Redirect["重定向到微信授权页"]
Redirect --> Callback["回调finish(code,state)"]
Callback --> CheckState{"state校验通过?"}
CheckState -- 否 --> ErrState["抛出异常并返回用户页"]
CheckState -- 是 --> GetToken["换取access_token/openid"]
GetToken --> GetUser["拉取用户信息"]
GetUser --> Assemble["组装sns数据<br/>nickname/头像/性别"]
Assemble --> Decide["调用SnsLoginService.resolve<br/>登录/绑定/注册"]
Decide --> End(["结束"])
SnsLoginService(统一登录协调)
- 职责
- 接收来自各 Provider 的 sns 数据,按优先级处理:
- 若 openid/unionid 已关联用户,则直接登录并跳转用户中心;
- 若当前已登录用户,则将 sns 绑定到该用户;
- 若允许免绑且未登录,则自动注册新用户并登录;
- 否则将 sns 数据写入 Session,跳转到绑定页面。
- 接收来自各 Provider 的 sns 数据,按优先级处理:
- 关键点
- loginIdMode 支持 'openid' 或 'unionid',影响查找已关联用户的策略。
- 自动注册时生成临时邮箱与密码,写入 user_sns 关联,并记录分销关系。
classDiagram
class SnsLoginService {
+resolve(sns, userProfile, loginIdMode, nobind) string
-insertUserSns(userId, sns) void
-autoRegister(sns) int
}
小程序登录(API)
- 职责
- 接收前端 wx.login() 返回的 code,调用 jscode2session 获取 openid/session_key(可选 unionid)。
- 根据 loginid_mode 决定是否必须 unionid。
- 支持手机号绑定与自动注册,最终返回登录态或绑定结果。
- 关键点
- ipRateLimit 防刷。
- 当 unionid 缺失且要求 unionid 模式时,明确报错。
- 绑定失败时记录审计日志并回滚事务。
依赖关系分析
- 插件层依赖服务层:WxloginProvider -> WxloginService
- 服务层依赖协调器:WxloginService -> SnsLoginService
- 小程序 API 独立于网页插件:WeixinController -> 微信接口 + 数据库
- 配置依赖:config.php 提供应用级密钥;插件配置由后台管理保存并通过 plugin() 读取
graph LR
P["WxloginProvider"] --> S["WxloginService"]
S --> C["SnsLoginService"]
A["WeixinController"] --> DB["数据库(user_sns/user)"]
S --> DB
CFG["config.php"] -.-> S
CFG -.-> A
性能与安全
- 性能
- 网络请求:微信授权与用户信息拉取均为外部 HTTP 调用,应关注超时与重试策略。
- 数据库查询:user_sns 表以 openid/unionid 作为快速定位键,建议确保索引合理。
- 会话状态:state 仅短期存储于 Session,避免持久化敏感信息。
- 安全
- state 校验:回调时必须严格比对 Session 中期望 state 与回调 state,防止 CSRF 攻击。
- 输入校验:nickname 非法字符过滤,避免注入与显示异常。
- 频率限制:小程序登录接口具备 IP 限流,防止暴力破解。
- 隐私保护:头像直链由微信返回,服务端不缓存敏感字段;unionid 仅在需要时参与匹配。
- 错误审计:登录失败、绑定异常等路径记录审计日志,便于追踪问题。
故障排查指南
- 常见错误与定位
- “无法获取微信授权信息”:检查回调 state 是否一致、code 是否过期、AppID/AppSecret 是否正确。
- “无法获取 unionid”:确认公众号/开放平台已开启 UnionID 机制,且在必要模式下启用 unionid。
- “请求微信接口失败”:检查网络连通性、证书验证、IP 白名单配置。
- “账户已被锁定”:查看登录失败次数与锁定策略,必要时解锁。
- 排查步骤
- 核对插件配置:开放平台与公众号的 AppID/AppSecret 是否分别正确。
- 检查回调地址:确保回调域名在微信后台配置。
- 查看日志:审计日志与错误堆栈,定位具体失败阶段。
- 复现最小用例:单独测试 code 换 token、token 换用户信息两个步骤。
结论
DouPHP 的微信登录插件采用清晰的插件-服务-协调器分层设计,既支持网页端(公众号/开放平台)OAuth2.0 授权,也兼容小程序 API 登录。通过 state 校验、UnionID 统一身份、严格的输入与错误处理,实现了安全可靠的第三方登录体验。开发者可基于此框架扩展更多社交登录渠道,并保持统一的登录流程与数据结构。
附录:开发指南与多端实现
网页授权登录(公众号/开放平台)
- 配置
- 在插件配置中填写开放平台 AppID/AppSecret(PC 扫码登录)与公众号 AppID/AppSecret(公众号内自动登录)。
- 流程
- 用户点击登录 -> 生成 state 并重定向到微信授权页 -> 回调校验 state -> 换取 token -> 拉取用户信息 -> 登录/绑定/注册。
- 关键点
- UA 判断:MicroMessenger 表示公众号环境,使用公众号 AppID;否则使用开放平台 AppID。
- scope:公众号使用 snsapi_userinfo,开放平台使用 snsapi_login。
- 回调地址:需与微信后台配置的回调域名一致。
小程序登录
- 配置
- 在系统配置中设置小程序 AppID/AppSecret。
- 流程
- 前端 wx.login() 获取 code -> 后端调用 jscode2session -> 获取 openid/session_key(可选 unionid)-> 查找/绑定/注册 -> 返回登录态。
- 关键点
- 支持手机号绑定与自动注册。
- 当要求 unionid 模式但缺失时,明确报错。
APP 登录(概念性说明)
- 若需支持 APP 端微信登录,通常采用微信开放平台的移动应用 OAuth2.0 流程。本项目当前插件聚焦网页与小程序,APP 端可扩展类似 WxloginService 的服务,复用 SnsLoginService 的统一协调逻辑。
- 注意
- 不同平台的 AppID/AppSecret 与回调地址需分别配置。
- 遵循各平台的安全规范与权限申请。
安全机制要点
- state 参数验证:回调时严格比对 Session 中的期望 state。
- token 刷新:access_token 有效期有限,需在业务侧按需刷新并妥善存储。
- 用户隐私保护:仅拉取必要字段,头像直链由微信返回,不缓存敏感信息。
- 频率限制:小程序登录接口具备 IP 限流,防止滥用。
UnionID 与多端统一
- 当配置 loginid_mode 为 unionid 时,系统优先按 unionid 匹配已关联用户,实现公众号、小程序、开放平台等多端账号统一。
- 若 unionid 缺失且要求 unionid 模式,将提示管理员处理。