Skip to content

微信登录与绑定手机号 ​

适用范围 ​

微信小程序、移动应用和公众号网页使用不同的授权入口,不能混用授权码。

场景客户端取得 code服务端交换接口
微信小程序uni.login({ provider: 'weixin' }) / wx.login/sns/jscode2session
移动应用微信 OpenSDK 授权回调/sns/oauth2/access_token
公众号网页网页 OAuth 授权重定向/sns/oauth2/access_token

微信小程序登录与自动注册 ​

  1. 客户端取得临时登录 code,通过 POST 交给业务服务端。
  2. 服务端从可信配置读取小程序 AppID、AppSecret,调用 https://api.weixin.qq.com/sns/jscode2session,传入 appid、secret、js_code 和 grant_type=authorization_code。
  3. 检查 HTTP 结果、微信错误码和非空 openid。返回的 unionid 是可选信息,不应作为登录的必需条件。
  4. 在事务中按 (AppID, openid) 查找身份;不存在时创建业务账号和身份绑定,存在时复用账号。数据库唯一约束与并发冲突处理共同保证不重复建号。
  5. 检查账号状态,签发业务 token。客户端后续请求携带业务 token,由业务拦截器认证。

小程序身份交换不返回网页 OAuth 的 access_token,也不自动提供手机号、头像或昵称。AppSecret 和 session_key 不返回客户端,不记录到日志;仅登录时无需持久化 session_key。用户取消、code 失效或请求失败时不能创建账号;重新登录应取得新 code。

以下示例主要说明 OAuth 身份交换;其中授权 URL 属于公众号网页,移动应用应使用 OpenSDK,小程序应使用上面的独立流程。示例中的用户表、会话签发和业务同步由应用自行实现。

OAuth 登录流程概述 ​

在移动端应用中,通过微信授权登录的流程如下:

  1. 调起微信授权:APP 调起微信客户端进行授权,用户同意后获取授权码 (code)。
  2. 传递 code 给后台:APP 将获取到的 code 通过接口传递给后台服务器。
  3. 服务器交换 code 获取 access_token 和 openid:后台服务器使用 appid、secret 和 code 向微信服务器请求,获取 access_token 和 openid。
  4. 获取用户信息:使用 access_token 和 openid 请求微信服务器,获取用户的详细信息。

下图展示了整个流程:

登录流程图

获取微信授权码 (code) ​

微信授权码 (code) 是用户通过微信授权登录时由微信服务器颁发的临时凭证。获取 code 的过程主要在移动端完成,但服务端也可以通过特定方式设置。

1. 公众号网页获取微信授权码 (code) 的步骤 ​

微信授权码 (code) 获取流程基于 OAuth 2.0 协议,具体步骤如下:

  1. 用户同意授权:

    • 应用发起授权请求,微信客户端展示授权页面,用户选择是否同意授权。

    • 通过微信提供的 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 攻击,微信会在回调时带上这个参数。
  2. 用户确认授权后获取 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 后,可以进一步请求微信服务器获取用户的详细信息。根据请求结果,可能会得到以下响应:

失败时的响应 ​

json
{
  "errcode": 40029,
  "errmsg": "invalid code, rid: 671f18cb-3abf86e0-5c326ea1"
}

成功时的响应 ​

json
{
  "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:

  1. 访问 微信开放平台。
  2. 注册并登录账号。
  3. 创建新的应用(如移动应用)。
  4. 获取 AppID 和 AppSecret,用于后续接口调用。

2. 将配置信息存入数据库 ​

将微信相关的配置信息存储在数据库中,以便后续调用。假设使用的数据库表为 tio_boot_admin_system_constants_config,配置示例如下:

json
{
  "appId": "wx7xxxx",
  "appSecret": "55xxxx"
}
  • 表结构说明:
    • key:配置的标识,这里为 systemWechatOpenConfig。
    • value:存储微信的 appId 和 appSecret。

3. 添加依赖 ​

在项目的依赖管理中统一管理 tio-wechat,以下仅展示依赖坐标;示例展示 OAuth 登录工具,不代表已封装小程序身份交换:

xml
<dependency>
  <groupId>nexus.io</groupId>
  <artifactId>tio-wechat</artifactId>
</dependency>

4. 添加配置类 ​

创建配置类 WechatConfig,用于初始化微信客户端:

java
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 类,处理微信登录逻辑:

java
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。
      • 最终返回登录结果。
  • 异常处理:

    • 获取 access_token 失败时返回失败响应。
    • 用户注册或同步失败时返回相应错误信息。

6. 接口层实现 ​

在接口层实现微信登录的 API,调用 WechatLoginService 进行处理:

java
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 不为空。

接口文档 ​

微信登录接口 ​

  • 接口地址:

    http://127.0.0.1/auth/loginByWechat
  • 请求方式:GET 或 POST

  • 请求参数:

    参数名类型必须描述
    codeString是微信授权码 (code)
  • 示例请求:

    http://127.0.0.1/auth/loginByWechat?code=091Tatml2H8qoe4S5Qml2svP1E3TatmP

参考文档 ​


结语 ​

至此,微信登录的整合已完成。通过本指南,您可以在移动端应用中实现微信授权登录功能,包括获取授权码、交换令牌、获取用户信息以及用户登录和注册流程的实现。如在实际操作中遇到问题,建议参考微信官方文档或相关技术社区寻求帮助。


注意事项:

  1. 安全性:

    • 确保 appSecret 不被泄露。
    • 使用 HTTPS 保护通信安全。
    • 防范 CSRF 攻击,合理使用 state 参数。
  2. 错误处理:

    • 对微信服务器返回的错误码进行详细处理,提升用户体验。
    • 日志记录异常情况,便于问题排查。
  3. 用户体验:

    • 优化授权页面,引导用户顺利完成授权。
    • 处理授权失败或取消的情况,提供友好的提示信息。
  4. 数据存储:

    • 合理存储和管理用户信息,遵守相关隐私政策和法规。

通过以上步骤和注意事项,您可以高效、安全地在移动端应用中集成微信登录功能,提升用户的登录体验。

绑定手机号 ​

绑定手机号是独立于微信身份登录的操作,需要已登录的业务会话,不能直接信任客户端提交的手机号或用户 ID。

  • 短信方式:服务端校验目标手机号和绑定用途的验证码,验证成功后执行绑定;验证码需设置有效期、一次性消费及发送和验证限流。
  • 微信小程序方式:用户主动点击 open-type="getPhoneNumber" 按钮,客户端把回调中的手机号授权 code 交给服务端,由服务端调用手机号接口取得已验证手机号。该 code 与 wx.login 的登录 code 不可混用。调用手机号接口所需的应用 access_token 由服务端获取并缓存,不是业务登录 token。
  • 使用统一规则规范化手机号,再在事务中检查归属与唯一约束。手机号已属于另一个账号时返回冲突并引导验证已有账号,不直接覆盖绑定或迁移余额、订单等业务数据。
  • 仅在相关能力已开通时展示微信手机号授权入口,并保留适用的短信验证方式。拒绝手机号授权不代表微信身份登录失败。

参考:小程序登录、获取手机号、uni.login。

MIT Licensed