微信登录与绑定手机号
适用范围
微信小程序、移动应用和公众号网页使用不同的授权入口,不能混用授权码。
| 场景 | 客户端取得 code | 服务端交换接口 |
|---|---|---|
| 微信小程序 | uni.login({ provider: 'weixin' }) / wx.login | /sns/jscode2session |
| 移动应用 | 微信 OpenSDK 授权回调 | /sns/oauth2/access_token |
| 公众号网页 | 网页 OAuth 授权重定向 | /sns/oauth2/access_token |
微信小程序登录与自动注册
- 客户端取得临时登录
code,通过 POST 交给业务服务端。 - 服务端从可信配置读取小程序 AppID、AppSecret,调用
https://api.weixin.qq.com/sns/jscode2session,传入appid、secret、js_code和grant_type=authorization_code。 - 检查 HTTP 结果、微信错误码和非空
openid。返回的unionid是可选信息,不应作为登录的必需条件。 - 在事务中按
(AppID, openid)查找身份;不存在时创建业务账号和身份绑定,存在时复用账号。数据库唯一约束与并发冲突处理共同保证不重复建号。 - 检查账号状态,签发业务 token。客户端后续请求携带业务 token,由业务拦截器认证。
小程序身份交换不返回网页 OAuth 的 access_token,也不自动提供手机号、头像或昵称。AppSecret 和 session_key 不返回客户端,不记录到日志;仅登录时无需持久化 session_key。用户取消、code 失效或请求失败时不能创建账号;重新登录应取得新 code。
以下示例主要说明 OAuth 身份交换;其中授权 URL 属于公众号网页,移动应用应使用 OpenSDK,小程序应使用上面的独立流程。示例中的用户表、会话签发和业务同步由应用自行实现。
OAuth 登录流程概述
在移动端应用中,通过微信授权登录的流程如下:
- 调起微信授权:APP 调起微信客户端进行授权,用户同意后获取授权码 (
code)。 - 传递
code给后台:APP 将获取到的code通过接口传递给后台服务器。 - 服务器交换
code获取access_token和openid:后台服务器使用appid、secret和code向微信服务器请求,获取access_token和openid。 - 获取用户信息:使用
access_token和openid请求微信服务器,获取用户的详细信息。
下图展示了整个流程:

获取微信授权码 (code)
微信授权码 (code) 是用户通过微信授权登录时由微信服务器颁发的临时凭证。获取 code 的过程主要在移动端完成,但服务端也可以通过特定方式设置。
1. 公众号网页获取微信授权码 (code) 的步骤
微信授权码 (code) 获取流程基于 OAuth 2.0 协议,具体步骤如下:
用户同意授权:
应用发起授权请求,微信客户端展示授权页面,用户选择是否同意授权。
通过微信提供的 OAuth 2.0 授权接口实现,典型的授权 URL 格式如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect参数说明:
appid:应用的 AppID(在微信开放平台申请)。redirect_uri:授权完成后跳转的回调地址,需要进行 URL 编码。response_type:固定为code。scope:授权作用域,常见值:snsapi_base:静默授权,仅能获取用户的openid。snsapi_userinfo:弹出授权页面,可获取用户的详细信息。
state:自定义参数,用于保持请求和回调的状态,防止 CSRF 攻击,微信会在回调时带上这个参数。
用户确认授权后获取
code:用户在微信中同意授权后,微信服务器会通过
redirect_uri将code返回给应用,格式如下:https://your_redirect_uri?code=CODE&state=STATE应用需要解析回调 URL,提取
code参数。
2. 同一用户对一个应用的 code 是否唯一
code 并非唯一,每次用户授权登录时,微信都会生成一个新的、一次性使用的 code。具体特点如下:
- 一次性使用:每个
code只能使用一次,用于交换access_token,重复使用将导致错误。 - 临时性:
code有效期,通常为 5 分钟,过期后无法使用。 - 不唯一:同一用户多次授权,每次生成不同的
code,不会重复。
因此,每次授权登录都需要使用最新生成的 code。
3. code 的有效期
code 有效期为 5 分钟,在此期间需要完成以下操作:
- 使用
code向微信服务器请求access_token和openid。 - 请求成功后,立即使用
access_token获取用户信息。
如果超过有效期,code 将失效,需引导用户重新发起授权获取新的 code。
获取 access-token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
在成功获取 access_token 和 openid 后,可以进一步请求微信服务器获取用户的详细信息。根据请求结果,可能会得到以下响应:
失败时的响应
{
"errcode": 40029,
"errmsg": "invalid code, rid: 671f18cb-3abf86e0-5c326ea1"
}
成功时的响应
{
"access_token": "85_BsrNgNL7cdqDegDu2r2wInW9_YdvJYyFH5VQ42Z4l1BQeQhB3Y2aFcAWknfpAjULtpGYBuC9j38TazTIAl0jAA1Z7g-nmsLJpUNSnDsj8-U",
"expires_in": 7200,
"refresh_token": "85_AIZ4NaZON5TPRU2111fRfzBFmcZ7wy4gVe37ImA5mwM5o6-_7iTUVFe5Pa7M6KbC4gd2wkcVu5ALUYISBfYcL87A1IbLJ1Ahltb6pchfI0o",
"openid": "os6Do6fUAqSIMUmJ0JANXFepwQ-k",
"scope": "snsapi_userinfo",
"unionid": "oPd1e5_J9QMYOEEjWcCokg8GU59s"
}
openid 和 unionid 的区别
openid:
- 用户在某个微信公众账号或小程序下的唯一标识。
- 不同的公众号或小程序,
openid是不同的。 - 主要用于区分同一应用内的不同用户。
unionid:
- 用户在微信开放平台下的唯一标识。
- 当多个微信应用(如多个公众号、小程序)绑定到同一开放平台账号时,同一用户在这些应用下的
unionid是相同的。 - 适用于跨应用的用户身份关联和数据整合。
总结:
- 使用
openid可以区分同一应用内的不同用户。 - 使用
unionid可以在多个绑定到同一开放平台的应用之间识别同一用户。
如果需要整合多个应用的用户数据,建议使用 unionid 进行关联。
获取用户信息
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
获取到的用户信息 见 类 nexus.io.wechat.model.WechatUserInfo
整合微信登录
整合微信登录涉及多个步骤,包括申请微信开发者资质、配置后台服务、实现登录逻辑等。以下是详细步骤:
1. 申请 AppID 和 AppSecret
在微信开放平台注册并创建应用,获取 AppID 和 AppSecret:
- 访问 微信开放平台。
- 注册并登录账号。
- 创建新的应用(如移动应用)。
- 获取
AppID和AppSecret,用于后续接口调用。
2. 将配置信息存入数据库
将微信相关的配置信息存储在数据库中,以便后续调用。假设使用的数据库表为 tio_boot_admin_system_constants_config,配置示例如下:
{
"appId": "wx7xxxx",
"appSecret": "55xxxx"
}
- 表结构说明:
key:配置的标识,这里为systemWechatOpenConfig。value:存储微信的appId和appSecret。
3. 添加依赖
在项目的依赖管理中统一管理 tio-wechat,以下仅展示依赖坐标;示例展示 OAuth 登录工具,不代表已封装小程序身份交换:
<dependency>
<groupId>nexus.io</groupId>
<artifactId>tio-wechat</artifactId>
</dependency>
4. 添加配置类
创建配置类 WechatConfig,用于初始化微信客户端:
package com.example.auth.app.config;
import com.example.auth.app.dao.SystemConfigDao;
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.jfinal.aop.Aop;
import nexus.io.wechat.client.Wechat;
import nexus.io.wechat.client.WechatClient;
import nexus.io.wechat.model.WechatOpenConfig;
import lombok.extern.slf4j.Slf4j;
@AConfiguration
@Slf4j
public class WechatConfig {
@Initialization(priority = 101)
public void config() {
// 从数据库获取微信配置信息
WechatOpenConfig systemWechatOpenConfig = Aop.get(SystemConfigDao.class).getSystemWechatOpenConfig();
log.info("初始化微信客户端 main");
// 初始化微信客户端
WechatClient wechatClient = new WechatClient("main", systemWechatOpenConfig);
Wechat.addClient(wechatClient);
}
}
- 类说明:
- 使用
@AConfiguration注解标识为配置类。 - 使用
@Initialization注解定义初始化方法,优先级设置为101。 - 从数据库中获取微信配置,并初始化
WechatClient。
- 使用
5. 实现 WechatLoginService
创建 WechatLoginService 类,处理微信登录逻辑:
package com.example.auth.app.services.wechat;
import com.example.auth.app.dao.SysUserInfoDao;
import com.example.auth.app.services.AuthService;
import com.example.auth.app.services.EmqxService;
import com.jfinal.kit.Kv;
import nexus.io.db.activerecord.Db;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.wechat.client.Wechat;
import nexus.io.wechat.client.WechatClient;
import nexus.io.wechat.model.WechatAccessToken;
import nexus.io.wechat.model.WechatUserInfo;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class WechatLoginService {
/**
* 通过微信授权码登录
* @param code 微信授权码
* @return 登录响应结果
*/
public RespBodyVo login(String code) {
WechatClient wechatClient = Wechat.use();
// 使用 code 获取 access_token
WechatAccessToken accessToken = wechatClient.getAccessToken(code);
if (accessToken == null) {
return RespBodyVo.fail("获取 access_token 失败");
}
if (accessToken.getErrcode() != null) {
return RespBodyVo.fail(accessToken.getErrmsg());
}
String openid = accessToken.getOpenid();
// 判断用户是否存在
String sql = "SELECT id FROM sys_user_info WHERE wx_open_id=? AND deleted=0";
Long userId = Db.queryLong(sql, openid);
AuthService authService = Aop.get(AuthService.class);
if (userId != null) {
// 用户已存在,执行登录
Kv loginResult = authService.doLogin(userId);
return RespBodyVo.ok(loginResult);
} else {
// 用户不存在,自动注册新用户
log.info("注册新用户: {}", openid);
String access_token = accessToken.getAccess_token();
// 获取用户详细信息
WechatUserInfo wechatUserInfo = wechatClient.getUserInfo(access_token, openid);
// 保存用户信息到数据库
SysUserInfoDao sysUserInfoDao = Aop.get(SysUserInfoDao.class);
userId = sysUserInfoDao.saveByWechatInfo(wechatUserInfo);
// 同步信息到 Emqx
String randomUsername = RandomUtil.randomString(32);
String randomPassword = RandomUtil.randomString(32);
EmqxService emqxService = Aop.get(EmqxService.class);
try {
emqxService.addAuthUser(userId.toString(), randomUsername, randomPassword);
} catch (Exception e) {
log.error("同步用户信息到 Emqx 失败", e);
}
if (userId == null) {
return RespBodyVo.fail("用户注册失败,请稍后再试。");
}
// 执行登录
Kv loginResult = authService.doLogin(userId);
return RespBodyVo.ok(loginResult);
}
}
}
方法说明:
- login(String code):通过微信授权码
code完成登录流程。- 获取
access_token和openid。 - 判断用户是否已存在,存在则直接登录。
- 不存在则自动注册新用户,并同步信息到 Emqx。
- 最终返回登录结果。
- 获取
- login(String code):通过微信授权码
异常处理:
- 获取
access_token失败时返回失败响应。 - 用户注册或同步失败时返回相应错误信息。
- 获取
6. 接口层实现
在接口层实现微信登录的 API,调用 WechatLoginService 进行处理:
public RespBodyVo loginByWechat(String code) {
if (StrUtil.isBlank(code)) {
return RespBodyVo.fail("授权码 (code) 不能为空");
}
return Aop.get(WechatLoginService.class).login(code);
}
- 方法说明:
- loginByWechat(String code):接收微信授权码
code,调用WechatLoginService处理登录。 - 参数校验:确保
code不为空。
- loginByWechat(String code):接收微信授权码
接口文档
微信登录接口
接口地址:
http://127.0.0.1/auth/loginByWechat请求方式:GET 或 POST
请求参数:
参数名 类型 必须 描述 code String 是 微信授权码 ( code)示例请求:
http://127.0.0.1/auth/loginByWechat?code=091Tatml2H8qoe4S5Qml2svP1E3TatmP
参考文档
结语
至此,微信登录的整合已完成。通过本指南,您可以在移动端应用中实现微信授权登录功能,包括获取授权码、交换令牌、获取用户信息以及用户登录和注册流程的实现。如在实际操作中遇到问题,建议参考微信官方文档或相关技术社区寻求帮助。
注意事项:
安全性:
- 确保
appSecret不被泄露。 - 使用 HTTPS 保护通信安全。
- 防范 CSRF 攻击,合理使用
state参数。
- 确保
错误处理:
- 对微信服务器返回的错误码进行详细处理,提升用户体验。
- 日志记录异常情况,便于问题排查。
用户体验:
- 优化授权页面,引导用户顺利完成授权。
- 处理授权失败或取消的情况,提供友好的提示信息。
数据存储:
- 合理存储和管理用户信息,遵守相关隐私政策和法规。
通过以上步骤和注意事项,您可以高效、安全地在移动端应用中集成微信登录功能,提升用户的登录体验。
绑定手机号
绑定手机号是独立于微信身份登录的操作,需要已登录的业务会话,不能直接信任客户端提交的手机号或用户 ID。
- 短信方式:服务端校验目标手机号和绑定用途的验证码,验证成功后执行绑定;验证码需设置有效期、一次性消费及发送和验证限流。
- 微信小程序方式:用户主动点击
open-type="getPhoneNumber"按钮,客户端把回调中的手机号授权 code 交给服务端,由服务端调用手机号接口取得已验证手机号。该 code 与wx.login的登录 code 不可混用。调用手机号接口所需的应用 access_token 由服务端获取并缓存,不是业务登录 token。 - 使用统一规则规范化手机号,再在事务中检查归属与唯一约束。手机号已属于另一个账号时返回冲突并引导验证已有账号,不直接覆盖绑定或迁移余额、订单等业务数据。
- 仅在相关能力已开通时展示微信手机号授权入口,并保留适用的短信验证方式。拒绝手机号授权不代表微信身份登录失败。
