Spring Boot实现微信公众号验证码登录(全流程详解+源码)
本文介绍了一种基于微信公众号的扫码验证码登录方案,特别适合个人订阅号开发者。通过分析传统登录方式的痛点,提出利用微信基础接口实现安全便捷的登录流程:用户扫码关注公众号后发送关键词获取验证码,在网页输入完成登录。文章详细讲解了业务时序图、公众号类型选择、内网穿透配置(使用Natapp)以及服务器接入流程,并提供了Maven依赖配置。该方案无需OAuth2.0授权,为个人开发者提供了一种低成本、高用户
前言:为什么选择“扫码-验证码”登录?
在 Web 应用开发中,用户登录是必不可少的一环。传统的账号密码登录往往让用户感到繁琐:忘记密码、重复注册、输入错误… 而微信扫码登录虽然便捷,但个人订阅号并不支持网页授权(OAuth2.0)。
这就陷入了一个死胡同吗?当然不是!
我们完全可以利用微信公众号的基础消息接口,通过“曲线救国”的方式实现一种安全、低成本且用户体验极佳的登录方案:用户扫码关注 -> 回复关键词 -> 获取验证码 -> 网页输入登录。
本文将带你从零开始,使用 Spring Boot + Hutool 打造一套完整的验证码登录系统,并附带内网穿透工具的使用指南。
一、 核心流程设计
这种登录方式的本质,是打通“微信用户”与“网页端”的信息壁垒。
1. 业务时序图 (Mermaid)
二、接入前准备
1. 公众号类型选择
微信公众号分为订阅号、服务号和企业号,不同账号类型的功能权限差异较大:
| 账号类型 | 认证要求 | 接口权限 | 适用场景 |
|---|---|---|---|
| 订阅号 | 个人/企业 | 基础接口 | 资讯推送 |
| 服务号 | 企业认证 | 全部接口 | 服务交互 |
| 测试号 | 无需认证 | 全部接口 | 开发测试 |
提示:为了展示最真实的开发流程,本文将使用真实的微信公众号进行实战演示,带你体验完整的接入过程。
2. 获取接口配置信息(重要变动)
在开始开发前,我们需要在微信后台配置服务器地址(URL)和令牌(Token)。
⚠️ 2025年12月1日 新版入口变动说明
以前我们都是直接在 微信公众平台 (mp.weixin.qq.com) 的“基本配置”里进行设置。
但从 2025年12月1日 起,微信官方将**“开发者工具”和“接口权限配置”**相关功能迁移到了全新的 微信开发者平台。
具体操作流程:
-
登录公众号:首先访问 微信公众平台 (mp.weixin.qq.com),登录您的公众号。
-
跳转新平台:在左侧菜单栏找到“开发”或“设置”相关选项,点击后会提示您跳转至新版地址,或者您也可以直接访问 微信开发者平台 (developers.weixin.qq.com/platform)。

-
扫码登录:使用绑定的管理员微信号扫码登录新平台。
-
填写配置:找到 “服务器配置” 或 “接口配置” 模块,填写以下信息:

- URL: 填写您的公网访问地址(需配合下一步的内网穿透),例如
http://你的域名/wx/callback。 - Token: 自定义一个字符串(如
mySecretToken),必须与 Java 代码保持一致。
注意:此时先不要点击“提交”,等 Java 代码写好并启动后再提交。
微信服务器需要访问你本地的开发电脑,所以需要“内网穿透”。
- 环境:JDK 17、IDEA、Natapp (或 Cpolar)
- 工具:Natapp
配置 Natapp:
-
注册与购买:访问 Natapp官网,注册后购买一个“免费隧道”。
-
配置隧道:在后台管理页面点击“配置”,将 本地端口 改为 8080(必须与 Spring Boot 端口一致)。
-
下载客户端:下载对应的客户端文件(如
natapp.exe)。
-
启动隧道:
-
打开命令行工具(CMD 或 PowerShell),进入
natapp.exe所在的目录。 -
运行以下命令启动(请将
YOUR_AUTHTOKEN替换为你后台显示的authtoken):natapp -authtoken=YOUR_AUTHTOKEN
-
-
获取地址:看到如下界面说明成功,复制
Forwarding后的公网地址(如http://xxxx.natappfree.cc)。
三、 完成服务器接入
1. 平台配置
微信公众号的对接,需要做一个 Spring Boot 服务,提供同名接口的不同请求方式:
- GET 请求:用于验证签名(微信服务器和你服务器的“握手”)。
- POST 请求:用于接收用户发送的消息。
配置步骤:
回到管理后台,找到 “接口配置信息”:
- URL: 填写 Natapp 生成的公网地址 + 接口路径,例如
http://xxxx.natappfree.cc/wx/callback。 - Token: 自定义一个字符串(如
mySecretToken),必须与 Java 代码保持一致。
注意:此时先不要点击“提交”,等 Java 代码写好并启动后再提交。
2. 典型消息处理流程

三、 代码实战
1. 依赖引入 (Maven)
除了 Spring Web,我们引入两个神器:
commons-codec: 也就是 Apache 的加密库,用于 SHA1 签名验证(不用自己手写算法了)。hutool-all: 国产 Java 工具包之光,用来生成随机数、管理带过期的缓存、解析 XML 等。
<dependencies>
<!-- 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- XML 解析 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 签名加密 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!-- Hutool 工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
2. 配置文件 (application.properties)
将配置外置,方便管理。
server.port=8080
# 微信公众号Token,请确保与微信后台配置一致
wechat.token=mySecretToken
3. 核心控制器:CallBackController
这是与微信服务器“对话”的唯一入口。
注意: 微信后台配置 URL 时,如果填的是 http://域名/wx/callback,那么我们的 Controller 就要监听这个路径。
@Slf4j
@RestController
@RequestMapping("/wx")
public class CallBackController {
@Value("${wechat.token}")
private String token; // 读取配置文件
// 使用 Hutool 的 TimedCache,自动管理过期,无需手动清理!
// Key: 验证码, Value: 用户OpenID
public static final TimedCache<String, String> LOGIN_CACHE = CacheUtil.newTimedCache(300 * 1000); // 5分钟
static {
LOGIN_CACHE.schedulePrune(1000); // 每秒检查一次过期
}
/**
* 1. 服务器验证接口 (GET)
* 微信后台点击“提交”时调用,用于确认服务器身份。
*/
@GetMapping("/callback")
public String verify(@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
// 1. 参数校验(防御性编程)
if (signature == null || timestamp == null || nonce == null || echostr == null) {
return "fail";
}
// 2. 签名校验 (安全核心)
// 将 token、timestamp、nonce 字典排序并 SHA1 加密
String sha1 = SHA1.getSHA1(token, timestamp, nonce);
// 3. 比对成功,原样返回 echostr
if (sha1 != null && sha1.equals(signature)) {
return echostr;
}
return "fail";
}
/**
* 2. 消息接收接口 (POST)
* 用户发送消息时,微信会将 XML 推送到这里。
*/
@PostMapping(value = "/callback", produces = "application/xml;charset=UTF-8")
public String handleMessage(@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce) {
// 同样需要验签,防止黑客伪造请求
if (!SHA1.checkSignature(token, timestamp, nonce, signature)) {
return "";
}
// 解析 XML
Map<String, String> msgMap = MessageUtil.parseXml(requestBody);
String fromUser = msgMap.get("FromUserName"); // 用户 OpenID
String toUser = msgMap.get("ToUserName"); // 公众号 ID
String content = msgMap.get("Content"); // 消息内容
// 业务逻辑:用户回复 "验证码"
if ("text".equals(msgMap.get("MsgType")) && "验证码".equals(content)) {
// 生成 6 位纯数字验证码
String code = RandomUtil.randomNumbers(6);
// 存入缓存
LOGIN_CACHE.put(code, fromUser);
// 构造回复 XML
// 注意:回复时,发送人是公众号(toUser),接收人是用户(fromUser)
return MessageUtil.textMessageToXml(fromUser, toUser,
"您的验证码是:" + code + "\n有效期为5分钟。");
}
return MessageUtil.textMessageToXml(fromUser, toUser, "回复 '验证码' 获取登录验证码。");
}
}
4. 工具类封装
为了代码整洁,我们把繁琐的逻辑抽离出来。
SHA1 签名工具类 (利用 commons-codec)
public class SHA1 {
public static String getSHA1(String token, String timestamp, String nonce) {
String[] arr = new String[]{token, timestamp, nonce};
Arrays.sort(arr); // 1. 字典序排序
StringBuilder sb = new StringBuilder();
for (String s : arr) sb.append(s);
return DigestUtils.sha1Hex(sb.toString()); // 2. SHA1 加密 (一行代码搞定!)
}
// 封装校验逻辑
public static boolean checkSignature(String token, String timestamp, String nonce, String signature) {
String sha1 = getSHA1(token, timestamp, nonce);
return sha1 != null && sha1.equals(signature);
}
}
MessageUtil XML 处理类
这里不需要复杂的对象映射,直接字符串拼接 XML 是性能最高且最不易出错的方式。
public static String textMessageToXml(String toUserName, String fromUserName, String content) {
return "<xml>" +
"<ToUserName><![CDATA[" + toUserName + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + fromUserName + "]]></FromUserName>" +
"<CreateTime>" + System.currentTimeMillis() / 1000 + "</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[" + content + "]]></Content>" +
"</xml>";
}
四、 前端交互实现
前端页面非常简单,核心逻辑是:轮询 或者 用户手动点击登录。这里演示最简单的“输入验证码登录”模式。
<!-- 核心代码片段 -->
<div class="login-box">
<!-- 请替换为你自己的二维码图片 -->
<img src="你的公众号二维码.jpg" alt="扫码关注">
<p>关注回复 <b>"验证码"</b> 获取登录码</p>
<input type="text" id="code" placeholder="输入6位验证码">
<button onclick="doLogin()">登录</button>
</div>
<script>
function doLogin() {
let code = document.getElementById("code").value;
// 调用后端接口
fetch('/login?code=' + code)
.then(res => res.text())
.then(data => {
if(data.includes("成功")) {
alert("登录成功!OpenID: " + data);
location.href = "/success.html";
} else {
alert(data);
}
});
}
</script>
五、 成果展示
经过以上步骤,我们已经完成了一个完整的闭环。让我们来看看最终的效果:


六、 避坑指南 & 最佳实践
-
Token 一致性:代码里的
wechat.token必须和微信后台填写的 Token 完全一致。如果不一致,后台提交配置时会提示“Token验证失败”。 -
明文模式:对于初学者,微信后台的“消息加解密方式”请务必选择 “明文模式”。如果选了安全模式,你的代码需要引入 AES 解密库,复杂度直线上升。
-
收发人反转:这是最容易犯错的地方!
- 接收消息时:
From是用户,To是公众号。 - 回复消息时:必须反过来,
To是用户,From是公众号。如果不反转,用户永远收不到消息(因为你发给公众号自己了)。
- 接收消息时:
-
Hutool 的 TimedCache:不要用
Map做缓存!因为 Map 不会自动清理过期数据,运行久了会内存泄漏。Hutool 的TimedCache是轻量级场景的最佳选择。
结语
通过这套方案,我们成功绕过了个人订阅号的权限限制,实现了一个体验流畅的扫码登录功能。这不仅展示了 Spring Boot 的灵活性,也体现了 Hutool 等工具库在提升开发效率上的巨大价值。
希望这篇详细的教程能帮助到你,如果有任何问题,欢迎在评论区留言交流!
更多推荐




所有评论(0)