简介
本指南面向在 DouPHP 框架中开发“社交登录”插件的开发者,围绕 OAuth2.0 授权流程、用户信息获取、账号绑定机制展开,结合微信、QQ、Google 三个现有插件的实现,系统讲解从授权跳转、回调处理、数据映射到安全校验的完整链路。文档同时提供可复用的开发模板与常见问题解决方案,帮助快速落地新的第三方平台接入。
项目结构
DouPHP 将社交登录能力以“插件”形式组织,每个平台一个独立插件目录,包含 Provider(对外接口)、Service(业务实现)和 manifest(插件元信息)。核心登录协调逻辑集中在 SnsLoginService,统一处理“已绑定直接登录、未绑定走绑定页、免绑自动注册”等策略。
graph TB
subgraph "插件层"
WX["微信登录<br/>WxloginProvider / WxloginService"]
QQ["QQ登录<br/>QqProvider / QqService"]
G["Google登录<br/>GoogleProvider / GoogleService"]
end
subgraph "核心服务"
SNS["SnsLoginService<br/>统一登录/绑定协调"]
end
WX --> SNS
QQ --> SNS
G --> SNS
核心组件
- 插件 Provider:实现 ConnectPluginProviderInterface,暴露 pluginId、meta、start、finish。负责把请求委派给 Service。
- 插件 Service:实现具体平台的 OAuth2.0 流程(构造授权 URL、处理回调、换取 token、拉取用户信息),并调用 SnsLoginService 完成登录/绑定。
- SnsLoginService:统一协调登录/绑定/注册流程,依据 openid/unionid 查找用户、处理已登录用户的绑定、支持免绑自动注册。
架构总览
下图展示“发起授权 → 平台回调 → 统一登录/绑定”的端到端流程。各平台 Service 差异仅在于授权端点、token 交换方式与用户信息获取方式,最终都汇聚到 SnsLoginService。
sequenceDiagram
participant U as "用户浏览器"
participant P as "插件Provider"
participant S as "插件Service"
participant O as "第三方OAuth平台"
participant C as "SnsLoginService"
U->>P : 点击“第三方登录”
P->>S : start(ConnectStartRequest)
S-->>U : 返回授权页URL含state
U->>O : 访问授权页并同意授权
O-->>U : 重定向回本站回调地址带code/state
U->>P : finish(ConnectCallbackPayload)
P->>S : finish(payload)
S->>O : 用code换取access_token
O-->>S : 返回token
S->>O : 用token拉取用户信息
O-->>S : 返回用户资料
S->>C : resolve(sns, userProfile, loginIdMode, nobind)
C-->>U : 返回最终跳转URL登录/绑定/注册
详细组件分析
微信登录(wxlogin)
- 授权流程
- 根据 UA 区分公众号与开放平台,选择对应 appid/appsecret。
- 生成 state 存入 Session,拼接微信授权 URL(PC 扫码或公众号内授权)。
- 回调处理
- 校验 state,使用 code 换取 access_token 与 openid。
- 拉取用户信息,过滤非法字符,构建 sns 数据。
- 根据配置决定 loginIdMode(openid 或 unionid),调用 SnsLoginService.resolve。
- 关键点
- 支持 unionid 模式,便于多应用统一身份。
- 通过 Config 控制是否允许“免绑自动注册”。
flowchart TD
Start(["开始"]) --> UA["判断UA选择凭证"]
UA --> State["生成state并写入Session"]
State --> AuthUrl["拼接微信授权URL并重定向"]
AuthUrl --> Callback["回调接收code/state"]
Callback --> CheckState{"state校验通过?"}
CheckState -- 否 --> Err["抛出异常并返回用户中心"]
CheckState -- 是 --> Token["用code换access_token/openid"]
Token --> Userinfo["拉取用户信息"]
Userinfo --> Map["映射为sns字段"]
Map --> Resolve["调用SnsLoginService.resolve"]
Resolve --> End(["结束"])
QQ登录(qq)
- 授权流程
- 读取插件配置 appid/appkey,生成 state 写入会话,构造 QQ 授权 URL。
- 回调处理
- 使用官方 SDK 获取 access_token 与 openid,再拉取用户信息。
- 映射昵称、头像、性别等字段,调用 SnsLoginService.resolve(固定 openid 模式)。
- 关键点
- 通过全局变量注入 SDK 配置,简化 SDK 初始化。
- 使用 get_user_info 权限范围。
sequenceDiagram
participant U as "用户"
participant QP as "QqProvider"
participant QS as "QqService"
participant QQ as "QQ互联"
participant C as "SnsLoginService"
U->>QP : 点击QQ登录
QP->>QS : start()
QS-->>U : 返回QQ授权页URL
U->>QQ : 授权同意
QQ-->>U : 回调携带code/state
U->>QP : finish()
QP->>QS : finish(payload)
QS->>QQ : SDK回调获取token/openid
QQ-->>QS : 返回token/openid
QS->>QQ : 拉取用户信息
QQ-->>QS : 返回用户资料
QS->>C : resolve(sns, userProfile, 'openid', false)
C-->>U : 跳转登录/绑定/注册
Google登录(google)
- 授权流程
- 读取 client_id/client_secret,生成 state 写入 Session,构造 Google 授权 URL(含 select_account)。
- 回调处理
- 校验 state,POST 换取 access_token,再用 Bearer token 拉取用户信息。
- 使用 sub 作为 openid,映射昵称、头像等,调用 SnsLoginService.resolve(固定 openid 模式)。
- 关键点
- 严格遵循 Google OAuth2/OpenID Connect 规范。
- redirect_uri 必须与 Google Cloud Console 完全一致。
sequenceDiagram
participant U as "用户"
participant GP as "GoogleProvider"
participant GS as "GoogleService"
participant GO as "Google OAuth"
participant C as "SnsLoginService"
U->>GP : 点击Google登录
GP->>GS : start()
GS-->>U : 返回Google授权页URL
U->>GO : 授权同意
GO-->>U : 回调携带code/state
U->>GP : finish()
GP->>GS : finish(payload)
GS->>GO : POST换取access_token
GO-->>GS : 返回token
GS->>GO : GET用户信息(Bearer)
GO-->>GS : 返回用户资料(sub/name/picture)
GS->>C : resolve(sns, userProfile, 'openid', false)
C-->>U : 跳转登录/绑定/注册
统一登录/绑定协调(SnsLoginService)
- 优先级策略
- 若 openid/unionid 已关联某用户:直接登录并跳转用户中心。
- 若当前已登录但未绑定:写入 user_sns 绑定记录,跳转个人资料绑定页。
- 若开启免绑且未登录:自动注册新用户并登录。
- 否则:将 sns 数据写入 session,跳转绑定页让用户选择绑定已有账号或注册新号。
- 数据模型
- user_sns 表存储 group、apptype、openid、unionid 等,用于跨平台身份识别与绑定。
flowchart TD
A["收到sns数据"] --> Find{"按openid/unionid查user_sns"}
Find -- 找到用户 --> Login["写登录态并跳转用户中心"]
Find -- 未找到 --> IsLogin{"当前是否已登录?"}
IsLogin -- 是 --> Bind["写入user_sns绑定记录"]
Bind --> Profile["跳转个人资料绑定页"]
IsLogin -- 否 --> Nobind{"是否允许免绑?"}
Nobind -- 是 --> AutoReg["自动注册新用户并登录"]
AutoReg --> Done["跳转用户中心"]
Nobind -- 否 --> Link["保存sns到session并跳转绑定页"]
Link --> Done
依赖关系分析
- 插件 Provider 仅做路由转发,实际逻辑在 Service。
- 所有 Service 均依赖 SnsLoginService 完成统一的登录/绑定/注册。
- 插件通过 manifest 声明 provider 类名,便于框架发现与加载。
classDiagram
class ConnectPluginProviderInterface {
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
class WxloginProvider
class QqProvider
class GoogleProvider
class WxloginService
class QqService
class GoogleService
class SnsLoginService {
+resolve(sns, userProfile, loginIdMode, nobind) string
}
WxloginProvider ..|> ConnectPluginProviderInterface
QqProvider ..|> ConnectPluginProviderInterface
GoogleProvider ..|> ConnectPluginProviderInterface
WxloginProvider --> WxloginService : "委托"
QqProvider --> QqService : "委托"
GoogleProvider --> GoogleService : "委托"
WxloginService --> SnsLoginService : "调用"
QqService --> SnsLoginService : "调用"
GoogleService --> SnsLoginService : "调用"
性能与安全考虑
- 安全要点
- 始终校验 state,防止 CSRF 攻击。
- 对昵称等用户输入进行非法字符过滤。
- 回调地址需与第三方平台后台配置严格一致。
- 使用 HTTPS 与正确的 SSL 验证选项。
- 性能建议
- 网络请求(token 交换、用户信息拉取)应设置超时与重试上限。
- 避免重复请求,必要时缓存短期结果(注意隐私与合规)。
- 合理拆分错误分支,减少不必要的 HTTP 调用。
故障排查指南
- 常见错误与定位
- “非法操作”:多为 state 校验失败或 Session 丢失,检查回调参数与状态存储。
- “无法获取授权信息”:检查 code 有效性、网络连通性与凭据配置。
- “无法获取用户信息”:检查 scope 权限、token 类型与 API 限流。
- “配置不完整”:核对插件配置项(appid/appsecret 或 client_id/client_secret)。
- 排查步骤
- 确认回调地址与平台后台一致。
- 打印/记录关键中间值(state、code、token、用户信息)。
- 逐步缩小问题范围(先验证 token 交换,再验证用户信息拉取)。
结论
DouPHP 的社交登录插件采用“Provider + Service + 统一协调服务”的分层设计,既保证了各平台差异化的实现隔离,又通过 SnsLoginService 实现了统一的登录/绑定/注册策略。基于此模式,新增第三方平台只需实现 Provider 与 Service,并在 meta 中声明配置项,即可快速集成。
附录:开发模板与最佳实践
- 插件目录结构
- Provider:实现 ConnectPluginProviderInterface,委托 Service。
- Service:实现 start/finish,封装平台特定 OAuth2.0 流程,调用 SnsLoginService.resolve。
- manifest:声明 plugin_group 与 provider 类名。
- 最小实现清单
- 配置项:至少包含客户端标识与密钥(如 appid/appsecret 或 client_id/client_secret)。
- 授权页:生成 state 并返回授权 URL。
- 回调处理:校验 state,换取 token,拉取用户信息,映射为 sns 字段。
- 登录/绑定:调用 SnsLoginService.resolve,传入 loginIdMode 与 nobind 策略。
- 最佳实践
- 严格校验 state,防 CSRF。
- 对用户输入进行合法性校验与过滤。
- 使用 HTTPS,正确配置 SSL 验证。
- 明确 scope,仅申请必要权限。
- 做好错误提示与日志记录,便于排障。
- 保持回调地址与平台后台一致。