微信 SDK 扫码登录业务文档
1. 文档说明
本文说明项目如何通过微信 SDK 完成用户身份认证、绑定本地用户和建立登录态,同时说明当前源码与完整的 PC 网站扫码登录之间的差异。
先给出结论:当前项目已经实现了“后端接收微信 OAuth2 回调并完成本地登录”,但没有实现完整的“前端生成扫码授权地址、展示二维码、扫码后回调”的闭环。代码中的 wx_open 命名也比较宽泛,从实际使用的 SDK 类型和字段来看,它更接近微信公众号网页 OAuth2 授权,而不一定是微信开放平台网站应用的 PC 扫码登录。
2. 业务目标
用户不需要在项目中重新注册账号和密码,可以使用微信身份登录项目。系统需要完成三件事:
- 让微信确认用户身份。
- 将微信身份和项目本地用户绑定起来。
- 使用项目自己的登录态保护后续业务接口。
微信只负责“认证用户是谁”,项目仍然负责用户角色、封禁状态、Session 和业务权限。
3. 两种微信登录场景
3.1 微信公众号网页授权
适用场景:用户在微信内打开项目网页,点击授权后登录。
典型授权地址是:
https://open.weixin.qq.com/connect/oauth2/authorize
常见作用域包括:
snsapi_base:静默获取基础身份信息,通常不弹授权确认页。snsapi_userinfo:用户同意后获取昵称、头像等信息。
3.2 微信开放平台网站扫码登录
适用场景:用户在 PC 浏览器打开网站,使用微信扫描网页二维码登录。
典型授权地址是:
https://open.weixin.qq.com/connect/qrconnect
常见请求参数如下:
appid=开放平台网站应用 AppID
redirect_uri=后端回调地址
response_type=code
scope=snsapi_login
state=随机状态值
两种流程后半段都需要后端用临时 code 换取访问令牌,再获取微信用户信息;区别主要在账号类型、授权地址、作用域和平台配置。
4. 当前项目的实现边界
当前源码包含:
GET /api/user/login/wx_open回调接口。wx.open.appId和wx.open.appSecret配置项。- 使用
wx-java-mp-spring-boot-starter提供的WxMpService。 - 通过 OAuth2 服务用
code换取access_token和微信用户信息。 - 根据
unionId查询或创建本地用户。 - 将本地用户写入项目 Session。
当前源码没有找到:
- 前端生成并跳转微信授权地址的代码。
- PC 端二维码展示页面。
state的生成和校验逻辑。- 独立的扫码登录轮询或 WebSocket 通知机制。
- 完整的网站应用扫码登录回调页面。
因此,当前项目不是启动后就能直接使用的完整微信扫码登录产品,而是已经写好了后端回调和本地账号绑定的核心部分。
5. 当前源码调用链
sequenceDiagram
participant U as 用户浏览器
participant W as 微信平台
participant C as UserController
participant S as UserService
participant DB as MySQL
U->>W: 打开授权地址并扫码/授权
W-->>U: 回调地址附带临时 code
U->>C: GET /api/user/login/wx_open?code=...
C->>W: 使用 code 换 access_token
W-->>C: 返回 access_token
C->>W: 获取微信用户信息
W-->>C: 返回 unionId、openid、昵称、头像
C->>S: userLoginByMpOpen(userInfo, request)
S->>DB: 根据 unionId 查询本地用户
alt 用户不存在
S->>DB: 创建本地用户
end
S->>S: 检查是否被封禁
S->>C: 写入项目 Session
C-->>U: 返回 LoginUserVO
6. 代码实现说明
6.1 配置微信应用
配置文件中的相关内容是:
wx:
open:
appId: xxx
appSecret: xxx
WxOpenConfig 使用 @ConfigurationProperties(prefix = "wx.open") 读取配置,并在第一次调用 getWxMpService() 时创建微信 SDK 服务对象:
读取 appId 和 appSecret
-> 创建 WxMpDefaultConfigImpl
-> 写入 appId 和 secret
-> 创建 WxMpServiceImpl
-> 缓存服务对象
appSecret 只能保存在后端,不能写进前端代码或二维码参数中。
6.2 接收微信回调
当前回调接口是:
GET /api/user/login/wx_open?code=微信返回的临时凭证
控制器从请求中获取 code,然后调用:
accessToken = wxService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo userInfo = wxService.getOAuth2Service()
.getUserInfo(accessToken, code);
code 具有短时有效、只能使用一次等特点。后端拿到它后立即向微信换取访问令牌,前端不应该自行使用 appSecret 调用微信接口。
6.3 获取微信身份
当前代码要求微信返回以下两个标识:
String unionId = userInfo.getUnionId();
String mpOpenId = userInfo.getOpenid();
两者的业务含义不同:
| 标识 | 含义 | 当前用途 |
|---|---|---|
unionId |
同一开放平台主体下较稳定的用户标识 | 查询和绑定本地用户 |
openid |
用户在当前公众号或应用下的标识 | 保存到 mpOpenId |
当前项目使用 unionId 作为本地账号匹配依据,并把 openid 保存到用户表中。若微信没有返回其中任意一个值,控制器会认为登录失败。
6.4 绑定本地用户
UserServiceImpl.userLoginByMpOpen() 的处理规则是:
根据 unionId 查询 user 表
-> 用户存在且被封禁:拒绝登录
-> 用户存在且正常:直接登录
-> 用户不存在:创建本地用户
-> 写入项目 Session
首次登录时,代码会保存:
unionId
mpOpenId
userName
userAvatar
项目不会保存微信密码,也不会把微信 access_token 当作本项目的长期登录凭证。
6.5 建立项目登录态
登录完成后,代码执行:
request.getSession().setAttribute(USER_LOGIN_STATE, user);
其中 Session 键是:
user_login
接口返回脱敏后的 LoginUserVO。后续请求由浏览器自动携带 JSESSIONID,项目再从 Session 中获取用户 id,并回 MySQL 查询最新用户状态。
因此,微信登录和项目登录是两层关系:
微信 OAuth2 认证
↓
unionId 绑定本地 user
↓
项目 Session 登录
↓
项目权限校验
7. 完整 PC 扫码登录建议流程
如果目标是“电脑网页显示二维码,用户用微信扫描登录”,建议补齐下面的流程。
7.1 微信平台准备
根据登录场景完成对应配置:
- 注册微信开放平台账号或符合要求的公众号主体。
- 创建网站应用或配置公众号网页授权能力。
- 获取真实的
AppID和AppSecret。 - 配置授权域名、回调域名和精确回调地址。
- 生产环境使用 HTTPS。
- 按微信平台当前规则完成主体认证或应用审核。
具体资质要求会随账号类型和平台规则变化,不能用普通字符串替代真实凭证。
7.2 前端发起授权
PC 网站扫码登录通常由后端生成带 state 的授权地址,前端跳转到微信:
https://open.weixin.qq.com/connect/qrconnect?
appid=APP_ID
&redirect_uri=ENCODED_CALLBACK_URL
&response_type=code
&scope=snsapi_login
&state=RANDOM_STATE
#wechat_redirect
二维码页面由微信提供,SDK 的作用主要是帮助后端完成换取令牌和获取用户信息,不是替代整套前端登录页面。
7.3 回调并完成登录
微信回调时,后端至少需要处理:
校验 state
-> 校验 code 非空
-> 用 code 换 access_token
-> 获取微信用户信息
-> 根据 unionId 查询本地用户
-> 创建或更新本地用户
-> 写入 Session 或签发项目 Token
-> 跳转回前端页面
如果使用前后端分离项目,回调接口可以在服务端完成登录后重定向到前端,并由前端调用 /api/user/get/login 确认当前登录用户。
8. 当前源码需要修正或补充的地方
8.1 首次建用户可能违反数据库约束
userLoginByMpOpen() 创建新用户时没有设置 userAccount 和 userPassword,但 create_table.sql 中这两个字段是 NOT NULL。
因此,首次微信登录可能插入失败。可选处理方式包括:
- 允许微信用户的账号密码字段为空。
- 为微信用户生成内部唯一账号和随机密码。
- 将登录方式拆成独立的第三方账号绑定表。
更适合长期维护的方案是增加第三方账号表,例如:
user
-> 用户基本信息
user_social_account
-> userId
-> platform = wechat
-> unionId
-> openId
8.2 unionId 应增加唯一约束
当前数据库只有 unionId 普通索引。synchronized (unionId.intern()) 只能防止同一个 JVM 内的并发重复创建,无法覆盖多实例部署。
生产环境应增加唯一约束,并在插入冲突时重新查询用户:
unique key uk_user_union_id (unionId)
8.3 增加 state 防止伪造回调
当前回调只接收 code,没有校验 OAuth2 的 state。完整流程应该由项目生成随机、短时有效的 state,并在回调时校验它,防止登录 CSRF 和回调串线。
8.4 核对 SDK 的用户信息参数
当前代码调用:
getUserInfo(accessToken, code)
接入时应根据项目实际使用的 wx-java 版本核对该方法第二个参数。部分版本中该参数表示语言,例如 zh_CN,而不是 OAuth2 的 code。如果方法签名确实要求语言参数,应改为对应语言值。
8.5 补齐前端授权入口
当前仓库中能看到后端回调,但没有完整的微信授权地址生成和前端扫码页面。因此还需要补充:
- 登录按钮。
- 授权地址生成接口或前端授权跳转。
- 回调后的前端页面处理。
- 登录成功后刷新当前用户状态。
- 登录失败和取消授权的提示。
9. 异常处理
| 异常场景 | 业务表现 | 建议处理 |
|---|---|---|
code 过期或重复使用 |
换取令牌失败 | 重新发起授权 |
AppID 或密钥错误 |
微信接口返回错误 | 检查环境变量和平台配置 |
| 回调域名未配置 | 微信无法正常回调 | 配置授权域名和 HTTPS |
未返回 unionId |
项目拒绝登录 | 检查账号绑定和授权范围 |
| 本地用户被封禁 | 禁止登录 | 返回明确的封禁提示 |
| MySQL 创建用户失败 | 登录失败 | 检查非空字段、唯一约束和事务 |
| Session Cookie 未保存 | 登录后仍未登录 | 检查跨域、Cookie 和代理配置 |
当前控制器把微信 SDK 异常统一转换成“登录失败,系统错误”,生产环境可以记录内部错误码,同时向用户返回更容易理解的提示。
10. 验收标准
完成完整扫码登录后,应至少验证:
- 未登录用户可以打开微信授权页面或扫码页面。
- 微信回调地址和域名配置正确。
code只能成功使用一次。- 已存在的
unionId不会重复创建本地用户。 - 首次登录可以成功写入本地用户。
- 被封禁用户无法通过微信登录。
- 登录成功后访问
/api/user/get/login能返回当前用户。 - 注销后 Session 被清除,受保护接口无法继续访问。
appSecret不会出现在前端、日志和 URL 中。- 重放旧
state或伪造state会被拒绝。
11. 源码定位
- UserController.java :微信登录回调入口。
- UserServiceImpl.java :微信用户查询、创建和 Session 登录。
- WxOpenConfig.java :微信 SDK 配置和服务对象初始化。
- UserService.java :微信登录服务接口定义。
- User.java :本地用户实体及微信标识字段。
- application.yml :
wx.open.appId和wx.open.appSecret配置。 - create_table.sql :用户表字段约束。
- pom.xml :wx-java-mp SDK 依赖。