文档目录
社交登录架构设计

简介

本架构设计文档面向 DouPHP 社交登录系统,聚焦统一抽象层、OAuth2.0 授权码流程、令牌与用户信息同步、账号绑定与合并、插件化扩展与安全加固。通过 Provider 接口规范与 Service 层封装,将不同第三方平台(如 Google、QQ、微信)的差异化实现收敛到一致的接入面;通过 SnsLoginService 统一落地策略,完成“已关联直接登录 / 已登录用户绑定 / 未登录自动注册 / 引导绑定”的多分支处理;结合安全中间件与配置项,保障会话、CSRF、XSS 等基础安全能力。

项目结构

社交登录相关代码主要分布在以下位置:

  • 插件层:各平台的 Provider 与 Service 实现(如 Google、QQ、微信),负责协议细节与外部交互。
  • 核心服务层:SnsLoginService 提供统一的 SNS 身份解析与落地策略。
  • 插件基础设施:ConnectPluginRegistry 扫描并发现 Connect 类插件,按约定加载 Provider。
  • API 端:小程序微信登录控制器,处理 code 换 session、手机号解密与绑定。
  • 安全与中间件:安全响应头、CSRF、API 认证等中间件与配置项。
graph TB
subgraph "插件层"
G["GoogleService"]
Q["QqService"]
WX["WxloginService"]
end
subgraph "核心服务"
SNS["SnsLoginService"]
end
subgraph "插件基础设施"
REG["ConnectPluginRegistry"]
end
subgraph "API端"
WXAPI["WeixinController"]
end
subgraph "安全与中间件"
SEC["SecurityHeadersMiddleware"]
CSRF["CsrfMiddleware"]
AUTH["UserAuthMiddleware"]
CFG["security.php"]
end
REG --> G
REG --> Q
REG --> WX
G --> SNS
Q --> SNS
WX --> SNS
WXAPI --> SNS
SEC --> CFG
CSRF --> CFG
AUTH --> CFG

核心组件

  • 统一抽象层(Provider 接口)
    • 所有社交登录插件需实现连接型 Provider 接口,暴露 pluginId、meta、start、finish 四个方法,使框架以统一方式发现与调用。
    • 示例:微信登录 Provider 声明插件 ID、元数据与配置字段,并将 start/finish 委托给具体 Service。
  • 平台服务层(Service)
    • 每个平台一个 Service,负责 OAuth2.0 授权码流程:生成 state、构造授权 URL、回跳校验 state、code 换 token、获取用户信息、标准化为内部 sns 数据结构。
    • 示例:GoogleService 使用 OpenID Connect scope,state 防 CSRF,token 与 userinfo 请求,最终调用 SnsLoginService 落地。
  • 用户信息标准化与落地(SnsLoginService)
    • 接收标准化后的 sns 数据(group/apptype/openid/unionid/nickname/avatar/sex),根据是否已关联、当前是否已登录、是否允许 nobind 等条件,决定直接登录、引导绑定或自动注册。
    • 写入 user_sns 表建立三方标识与本地用户的关联。
  • 插件注册与发现(ConnectPluginRegistry)
    • 扫描插件目录下的 manifest.php,自动发现并缓存 Provider 实例,支持 has() 查询与 provider() 获取。
  • API 端微信登录(WeixinController)
    • 小程序端通过 code 换取 session_key 与 openid/unionid,进行绑定或自动注册,并维护 user_sns 关联。

架构总览

社交登录采用“插件 Provider + 平台 Service + 统一协调器”的分层架构:

  • 前端/客户端触发登录,进入对应插件的 Provider.start,返回第三方授权页 URL。
  • 用户在第三方平台授权后回调至 Provider.finish,Service 完成 state 校验、code 换 token、获取用户信息。
  • 标准化后的 sns 数据交由 SnsLoginService.resolve 决策:已关联则登录;已登录用户则绑定;未登录且允许 nobind 则自动注册;否则跳转绑定页。
  • 安全中间件在边界处提供 CSRF、安全头、限流与会话硬化等防护。
sequenceDiagram
participant U as "用户"
participant P as "插件Provider"
participant S as "平台Service"
participant T as "第三方平台"
participant C as "SnsLoginService"
participant A as "认证/会话"
U->>P : 发起登录
P->>S : start(ConnectStartRequest)
S-->>U : 返回授权页URL
U->>T : 授权
T-->>P : 回调 finish(ConnectCallbackPayload)
P->>S : finish(payload)
S->>T : 用code换access_token
T-->>S : 返回token
S->>T : 获取用户信息
T-->>S : 返回userinfo
S->>C : resolve(sns, userProfile, mode, nobind)
C-->>A : 已关联 -> 写登录态
C-->>A : 已登录 -> 绑定user_sns
C-->>A : 未登录+nobind -> 自动注册并登录
C-->>U : 未登录+非nobind -> 跳转绑定页

详细组件分析

统一抽象层:Connect 插件 Provider 接口

  • 职责
    • 声明插件 ID、元信息与配置项,供管理后台展示与编辑。
    • 对外暴露 start/finish 两个入口,分别用于发起授权与处理回调。
  • 关键点
    • meta 中的 config 字段定义可配置项(如 appid/appsecret)。
    • start/finish 通常委托给具体 Service,保持 Provider 薄壳化。
  • 示例
    • 微信登录 Provider 定义了开放平台与公众号两套 appid/appsecret 配置,并通过 service 委派逻辑。
classDiagram
class ConnectPluginProviderInterface {
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
class WxloginProvider {
-service : WxloginService
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
ConnectPluginProviderInterface <|.. WxloginProvider

平台服务层:Google OAuth2.0/OpenID Connect 实现

  • 流程
    • start:生成 state 存入 Session,构造授权 URL(含 scope 与 prompt)。
    • finish:校验 state 防 CSRF;用 code 换 access_token;获取 userinfo;标准化为 sns;调用 SnsLoginService.resolve。
  • 安全要点
    • state 一致性校验,拒绝非法操作。
    • 对昵称等用户输入进行非法字符过滤。
  • 数据映射
    • 将 sub 作为 openid,nickname/picture 映射为 nickname/avatar,性别默认 0。
flowchart TD
Start(["开始"]) --> GenState["生成state并存储"]
GenState --> BuildUrl["构造授权URL"]
BuildUrl --> Redirect["跳转到第三方授权页"]
Redirect --> Callback{"回调finish"}
Callback --> VerifyState{"state匹配?"}
VerifyState -- 否 --> Error["抛出异常并返回用户页"]
VerifyState -- 是 --> FetchToken["用code换access_token"]
FetchToken --> TokenOk{"token获取成功?"}
TokenOk -- 否 --> Error
TokenOk -- 是 --> FetchUserinfo["获取用户信息"]
FetchUserinfo --> UserOk{"用户信息有效?"}
UserOk -- 否 --> Error
UserOk -- 是 --> Normalize["标准化sns数据"]
Normalize --> Resolve["调用SnsLoginService.resolve"]
Resolve --> End(["结束"])

用户信息标准化与账号绑定:SnsLoginService

  • 输入
    • sns:包含 group/apptype/openid/unionid/nickname/avatar/sex。
    • userProfile:当前已登录用户(可为空)。
    • loginIdMode:openid 或 unionid。
    • nobind:是否允许未登录用户直接注册。
  • 决策流程
    • 若 openid/unionid 已关联本地用户:写登录态并跳转用户中心。
    • 若当前已登录但未绑定:写入或更新 user_sns 记录,跳转用户 SNS 管理页。
    • 若未登录且 nobind=true:自动注册新会员并登录。
    • 否则:将 sns 数据写入 Session,跳转绑定页。
  • 数据持久化
    • 写入 user_sns 表,包含 user_id/group/openid/unionid/apptype/created_at。
flowchart TD
In(["resolve入口"]) --> Normalize["补齐sns默认值"]
Normalize --> Lookup{"查找已关联用户"}
Lookup --> |找到| Login["写登录态并跳转用户中心"]
Lookup --> |未找到| CheckProfile{"当前已登录?"}
CheckProfile --> |是| BindCheck{"是否已绑定该sns?"}
BindCheck --> |是| Update["更新user_sns记录"]
BindCheck --> |否| Insert["插入user_sns记录"]
Update --> ToSnsPage["跳转用户SNS页面"]
Insert --> ToSnsPage
CheckProfile --> |否| Nobind{"nobind=true?"}
Nobind --> |是| AutoReg["自动注册并登录"]
Nobind --> |否| SessionSet["sns写入Session"]
SessionSet --> ToLink["跳转绑定页"]
AutoReg --> End(["结束"])
ToSnsPage --> End
ToLink --> End

API 端微信小程序登录:WeixinController

  • 流程
    • 校验 IP 限流;从配置读取小程序 appid/appsecret;用 code 换取 openid/unionid/session_key。
    • 若 unionid 缺失且模式要求 unionid,则报错。
    • 根据 openid/unionid 查找已关联用户;若存在且未锁定,则登录;否则走绑定或自动注册。
    • 写入 user_sns 并更新登录次数。
  • 与 PC 端差异
    • 小程序端使用 jscode2session 流程,不等同于 PC 端插件 OAuth 流程。
sequenceDiagram
participant M as "小程序"
participant C as "WeixinController"
participant W as "微信API"
participant DB as "数据库"
participant SNS as "SnsLoginService"
M->>C : 提交code/rawData/iv/encryptedData
C->>W : jscode2session(code,appid,secret)
W-->>C : 返回openid/unionid/session_key
C->>DB : 查询user_sns关联
alt 已关联
C->>C : 检查账户锁定状态
C->>C : 登录并返回结果
else 未关联
C->>SNS : 绑定或自动注册
SNS-->>C : 返回结果
C->>DB : 写入user_sns并更新登录次数
end
C-->>M : 返回bind/用户信息

插件注册与发现:ConnectPluginRegistry

  • 功能
    • 扫描 PLUGIN_PATH 下各插件目录的 manifest.php,构建 Provider 类名映射。
    • 提供 has(pluginId) 判断是否支持某登录插件;provider(pluginId) 获取实例。
    • 通过容器创建 Provider 实例,并缓存以避免重复实例化。
  • 意义
    • 解耦平台实现与框架调度,新增平台只需遵循接口与清单即可被自动发现。

依赖关系分析

  • 松耦合
    • Provider 仅依赖接口与 Service,不感知具体业务细节。
    • Service 依赖 SnsLoginService 进行统一落地,避免在各平台重复实现绑定逻辑。
  • 关键依赖链
    • ConnectPluginRegistry -> Provider -> Service -> SnsLoginService -> 认证/会话/数据库。
    • API 端 WeixinController -> 微信API -> 数据库 -> SnsLoginService。
  • 潜在风险
    • 若 SnsLoginService 变更,需评估对多平台的影响。
    • 第三方 API 超时或失败时,应做好重试与降级策略。
graph LR
REG["ConnectPluginRegistry"] --> PROV["Provider"]
PROV --> SVC["Platform Service"]
SVC --> SNS["SnsLoginService"]
SNS --> AUTH["认证/会话"]
SNS --> DB["数据库(user/user_sns)"]
API["WeixinController"] --> WXAPI["微信API"]
API --> DB
API --> SNS

性能考虑

  • 网络请求优化
    • 第三方 API(token、userinfo)建议使用连接池与超时控制,避免阻塞主线程。
  • 缓存策略
    • 可将用户信息短期缓存(如基于 openid/unionid 的只读缓存),减少重复请求。
  • 数据库访问
    • 批量写入 user_sns 时注意事务与索引,避免锁竞争。
  • 限流与退避
    • 对频繁登录尝试进行限流(IP 级),防止暴力破解与资源耗尽。

故障排查指南

  • 常见错误与定位
    • state 不匹配:检查 Session 中保存的 state 与回调参数是否一致,确认跨域与 Cookie 设置。
    • code 无效或过期:确认授权页 redirect_uri 与第三方平台配置一致,检查网络连通性。
    • 无法获取用户信息:检查 access_token 权限范围(scope)与有效期。
    • 绑定失败:核对 user_sns 唯一约束与 group/apptype 维度,确保无冲突。
  • 安全相关
    • CSRF 防护:后台表单提交需携带 CSRF 令牌,由 CsrfMiddleware 校验。
    • 安全头:通过 SecurityHeadersMiddleware 下发 X-Frame-Options、Referrer-Policy 等。
    • 会话硬化:依据 security.php 配置 httponly、secure、samesite、use_strict_mode。
  • 日志与审计
    • 登录失败与异常路径建议记录审计日志,便于追踪问题根因。

结论

DouPHP 社交登录系统通过 Provider 接口与 Service 分层实现了多平台的统一接入;SnsLoginService 提供了清晰、可扩展的账号绑定与落地策略;ConnectPluginRegistry 使得新平台接入仅需遵循约定即可被自动发现;安全中间件与配置项保障了会话、CSRF、XSS 等基础安全能力。整体架构具备高内聚、低耦合、易扩展的特点,适合持续集成更多第三方平台与自定义用户字段、权限映射等扩展需求。

附录

  • 扩展机制建议
    • 新平台接入:实现 ConnectPluginProviderInterface,编写 manifest.php 与 Service,复用 SnsLoginService 流程。
    • 自定义用户字段:在 autoRegister 或绑定流程中扩展用户资料映射,注意字段合法性校验。
    • 权限映射:可在登录后根据第三方角色/组映射到本地权限体系,建议在认证完成后注入。
  • 参考实现路径
    • 插件 Provider:WxloginProvider.php
    • 平台 Service:GoogleService.php、QqProvider.php
    • 统一落地:SnsLoginService.php
    • API 端微信登录:WeixinController.php
    • 安全配置与中间件:security.php、SecurityHeadersMiddleware.php、CsrfMiddleware.php、UserAuthMiddleware.php
添加日期:2026-10-05