news 2026/10/2 6:45:27

WGCLOUD二次开发:为监控平台添加短信验证码登录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WGCLOUD二次开发:为监控平台添加短信验证码登录

“WGCLOUD支持短信登录系统吗”——这个问题我在不少运维群里看到过。WGCLOUD作为一款开源的服务器监控平台,很多团队拿它来做主机资源监控、告警通知,用着用着就开始琢磨登录方式的事。毕竟现在大家手机不离手,短信验证码登录几乎是企业内部系统的标配需求,谁也不想记一堆复杂的密码。所以我把这个问题拆开聊透:原生支持情况、能不能改、怎么改、改了之后会遇到哪些坑。

WGCLOUD支持短信登录系统吗

先说结论:WGCLOUD原生版本默认不支持短信验证码登录,它目前的登录认证走的是用户名+密码的传统方式。但从架构和源码层面看,它是可以支持短信登录的,只是需要你自己动手做二次开发。这篇文章我就基于WGCLOUD的实际源码结构和认证流程,把短信登录这件事完整拆解一遍,包括可行性分析、改造思路、核心代码实现和排坑经验。

1. 先搞清楚WGCLOUD的登录认证机制

1.1 原生登录流程是什么

WGCLOUD的服务端基于Spring Boot框架,前端页面登录请求会提交到后端的登录接口,由Controller处理。核心逻辑是接收用户提交的用户名和密码,然后调用UserService里的方法去数据库表user里比对账号密码(密码存储的是MD5加密值)。校验通过后,将用户信息写入Session,后续请求都依赖这个Session来识别身份。

整个链路可以简化成:前端表单提交 -> 登录Controller -> 查询用户表 -> 校验密码 -> 写入Session。

换句话说,WGCLOUD的认证模型就是一个标准的“用户名+密码+Session”的老牌Web应用。它没有集成任何第三方认证体系,也没有预留类似验证码登录的开关配置。所以你翻遍配置文件也只能设置密码强度、登录失败锁定这类功能,找不到短信登录相关选项。

1.2 为什么原生不支持短信登录

想明白这个问题,得从WGCLOUD的定位说起。它是一套轻量级监控系统,核心价值在“监控”而不是“账号体系”。开发团队优先做的是Agent采集、数据展示、告警通知这些核心能力,认证只做到“够用就行”。短信登录需要额外对接短信服务商、维护验证码存储、处理发送频率和过期策略,这些都是成本。对于开源版本来说,功能优先级不会排到它前面。

但这不意味着短信登录和WGCLOUD无缘。它的代码开源,而且登录逻辑集中在少数几个文件里,只要了解了认证原理,完全可以在不破坏原有功能的前提下,把短信登录作为第二种登录方式嵌进去。接下来我重点讲怎么实现。

2. 短信登录方案的可行性与设计思路

2.1 三种实现路径对比

想给WGCLOUD加上短信登录,无非三条路。第一条是在前端登录页加一个“短信登录”Tab,通过验证码接口换取临时凭证,再用这个凭证登录后端;第二条是直接复用现有的用户名密码登录入口,把短信验证码临时拼成一个一次性密码;第三条是引入外部统一认证平台(比如CAS、OAuth2),把短信登录交给认证中心处理。对比一下:

方案改造量安全性维护成本适用场景
前端Tab+独立验证码接口较大,需改前端和新增后端接口高,验证码独立认证中面向正式生产环境
一次性密码拼接小,改登录校验逻辑低,密码易冲突且难管理低临时演示或测试
外部统一认证较大,需集成SSO高,依赖运维能力高已有认证中心的企业

我推荐第一种。虽然改造量虽然不是最小的,但它在安全性和可用性之间取得了最好的平衡。短信验证码本身就是一个完整的认证因子,不应该和密码混在同一个字段里,独立接口会让后续排查和维护都更舒服。

2.2 技术选型与准备工作

做这个改造前,你需要准备以下东西:

  • WGCLOUD服务端源码包(建议下载和线上版本一致的源码,我这边用的是3.x版本,不同版本细节会有差异)
  • 一个短信服务商账号,国内常见的阿里云短信服务、腾讯云短信,或者你公司自建的短信网关
  • Maven、JDK1.8+、IDE,以及能连上WGCLOUD数据库的客户端
  • 一份能够收发短信的手机号(测试用)

短信服务商这块不用纠结,选你公司已经在用的那个,别为了一个功能引入第二个短信供应商,不然接下去对账、申请签名模板都要多忙一轮。

2.3 核心设计要点

我把整个方案抽象成四个关键点:

第一,验证码的生成和存储。验证码用6位纯数字,服务端生成后存储到Redis,key建议格式是sms:login:{手机号},value是验证码,有效期5分钟。没有Redis的话可以先用内存Cache代替,但生产环境还是建议上Redis,因为WGCLOUD的服务器可能会重启,内存缓存丢失会导致用户手机上验证码还在、服务端却验证不了,体验很差。

第二,发送频率限制。同一个手机号60秒内只能发送一次验证码,一天同一个号码最多发送10次,防止被恶意刷量。这些限制参数放到配置文件里,方便调整。

第三,登录状态管理。短信登录成功之后,仍然复用WGCLOUD原有的Session机制,也就是把当前用户写入Session,这样短信登录和密码登录在登录后体验完全一致。

第四,账号绑定关系。短信登录必须依赖手机号和WGCLOUD用户账号之间的绑定关系。原生的user表里是没有手机号字段的,需要在用户表扩展一个字段,或者新建一张绑定表。考虑到改动最小,直接给数据库用户表加一列phone更直接。

3. 实操:给WGCLOUD二次开发,实现短信验证码登录

3.1 环境准备与源码编译

先确认你的WGCLOUD版本。我这边以当前主流版本为例,服务端源码是标准的Maven项目,拿到源码后先导入IDE,让Maven自动下载依赖。注意JDK版本,WGCLOUD服务端要求JDK1.8,太新的JDK可能编译不过。

编译之前,先检查一下pom.xml里有没有spring-boot-starter-data-redis依赖,如果没有就加上。同时加一个HTTP客户端的依赖,我用的是hutool-all工具包,里面封装的HttpUtil发请求写起来比较省事。如果你不想用Hutool,用Spring的RestTemplate也行。

依赖部分在pom.xml里加下面的内容:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency>

加完后刷新一下Maven,确认依赖都下载成功,然后先直接编译一次原版源码,确保环境没问题。这一步别省,不然改了代码后分不清是环境问题还是代码问题。

3.2 数据库增加手机号字段

用数据库客户端连上WGCLOUD的数据库,找到user表,执行下面的SQL:

ALTER TABLE `user` ADD COLUMN `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号' AFTER `username`;

然后给你已有的管理员账号绑定手机号:

UPDATE `user` SET `phone` = '13800000000' WHERE `username` = 'admin';

注意,这里的表前缀和字段名以你实际版本为准。有些版本的表名是t_user或者有统一前缀,先看下数据库里的真实表结构再动手。

3.3 编写短信发送和验证码存取工具类

先把验证码的生成、存储、校验封装成一个服务类,我把它命名为SmsCodeService。

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @Service public class SmsCodeService { @Autowired private StringRedisTemplate redisTemplate; private static final String SMS_LOGIN_PREFIX = "sms:login:"; private static final long EXPIRE_MINUTES = 5; public String generateCode(String phone) { // 生成6位随机数字验证码 int code = ThreadLocalRandom.current().nextInt(100000, 999999); String key = SMS_LOGIN_PREFIX + phone; // 存储到Redis,有效期5分钟 redisTemplate.opsForValue().set(key, String.valueOf(code), EXPIRE_MINUTES, TimeUnit.MINUTES); return String.valueOf(code); } public boolean verifyCode(String phone, String code) { String key = SMS_LOGIN_PREFIX + phone; String savedCode = redisTemplate.opsForValue().get(key); if (savedCode != null && savedCode.equals(code)) { // 验证通过之后立即删除验证码,防止重复使用 redisTemplate.delete(key); return true; } return false; } }

这里有个关键细节:验证码校验成功后必须立即删除Redis里对应的记录。很多第一次写验证码逻辑的人容易漏掉这一点,结果同一个验证码能被反复使用,一旦泄露出去就是漏洞。

短信发送这块,以阿里云短信服务为例,核心步骤就是调API。它的SDK会稍微繁琐一点,但逻辑不复杂:

import cn.hutool.json.JSONObject; import cn.hutool.http.HttpUtil; public class SmsSender { private static final String SEND_URL = "https://dysmsapi.aliyuncs.com/"; // 实际接入时,这里的accessKeyId、accessKeySecret、signName、templateCode // 都应该从配置文件中读取 private static final String ACCESS_KEY_ID = "yourAccessKeyId"; private static final String ACCESS_KEY_SECRET = "yourAccessKeySecret"; public static boolean sendLoginCode(String phone, String code) { // 阿里云新版SDK需要构建请求,推荐直接用官方SDK // 这里为了演示,只说明大致流程 // 实际开发中请使用阿里云官方短信服务SDK(com.aliyun:dysmsapi20170525) return true; } }

为了保证博文可以照着做,这里建议直接用阿里云的官方SDK,而不是用HTTP拼接请求。原因很简单:官方SDK已经帮你处理了签名、时间戳这些容易出错的细节,你用HttpUtil手写请求,一个参数顺序不对就可能报签名错误,调试成本很高。SDK引入方式:

<dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.24</version> </dependency>

3.4 新增发送短信验证码接口

在WGCLOUD的Controller目录下新建一个SmsLoginController,提供两个接口:一个发送验证码,一个执行短信登录。

发送验证码接口需要做频率限制。用Redis记录上次发送时间,同一个手机号在60秒内重复请求时直接拒绝:

@RestController @RequestMapping("/api/sms") public class SmsLoginController { @Autowired private SmsCodeService smsCodeService; @Autowired private StringRedisTemplate redisTemplate; /** * 发送登录验证码 */ @PostMapping("/sendLoginCode") public Result sendLoginCode(@RequestBody JSONObject params) { String phone = params.getStr("phone"); if (phone == null || !phone.matches("^1\\d{10}$")) { return Result.error("手机号格式不正确"); } String freqKey = "sms:login:freq:" + phone; String lastTime = redisTemplate.opsForValue().get(freqKey); long now = System.currentTimeMillis(); if (lastTime != null && (now - Long.parseLong(lastTime)) < 60_000) { return Result.error("请求过于频繁,请稍后再试"); } String code = smsCodeService.generateCode(phone); // 调用短信服务商接口发送验证码 boolean sendOk = SmsSender.sendLoginCode(phone, code); if (!sendOk) { return Result.error("验证码发送失败"); } redisTemplate.opsForValue().set(freqKey, String.valueOf(now), 60, TimeUnit.SECONDS); return Result.success("验证码已发送"); } }

这里有个坑:如果短信服务商接口响应很慢,发送耗时可能超过几秒,而Redis的60秒频率限制记录在发送成功后才写入,这就导致连续点击发送按钮时,第一次发送未结束、第二次请求进来,频率记录还没写入,照样可以发送。解决办法是提前记录发送中状态,或者依赖短信服务商的云端流控,但更稳妥的办法是前端在收到发送请求后立即禁用按钮60秒,同时后端也做校验,双保险。

3.5 实现短信登录接口

短信登录的核心逻辑就是:校验验证码,根据手机号找到对应的WGCLOUD用户,然后写入Session。写入Session的方式要跟原版登录完全一致,才会不破坏后续的访问控制逻辑。

先看一下原版的登录成功是怎么写Session的。一般代码会是这样:request.getSession().setAttribute("user", userObj),或者通过Spring Security框架管理。不同版本的WGCLOUD处理方式不同,这步必须直接翻源码确认。找到那行之后,把用户对象设置成你查询到的用户即可。

/** * 短信验证码登录 */ @PostMapping("/loginBySms") public Result loginBySms(@RequestBody JSONObject params, HttpServletRequest request) { String phone = params.getStr("phone"); String code = params.getStr("code"); if (phone == null || code == null) { return Result.error("参数错误"); } // 1. 校验验证码 boolean verifyOk = smsCodeService.verifyCode(phone, code); if (!verifyOk) { return Result.error("验证码错误或已过期"); } // 2. 根据手机号查用户 User user = userService.findByPhone(phone); if (user == null) { return Result.error("该手机号未绑定WGCLOUD账号"); } // 3. 写入Session(具体写法和WGCLOUD源码保持一致) request.getSession().setAttribute("user", user); request.getSession().setAttribute("loginType", "sms"); return Result.success("登录成功"); }

UserService里要新增一个findByPhone方法,对应的Mapper加一个查询SQL:

SELECT * FROM `user` WHERE `phone` = #{phone}

这里有一个很容易踩的坑:WGCLOUD的Session存储格式不一定只是user这个属性,登录成功可能还会写入对应权限ID、角色标识等。你最好把原版密码登录成功的代码原封不动复制过来,只替换掉前面的用户名密码校验逻辑,改成验证码校验,这样Session内容就不会漏。我是这么做的:先看原版登录成功代码,把那段代码抽到一个公共方法,比如handleLoginSuccess(request, user),然后短信登录里也调用这个公共方法,两套登录走同一个登录后逻辑。

3.6 前端页面改造

WGCLOUD的前端技术栈是JSP + jQuery,登录页面通常位于/src/main/webapp/WEB-INF/views下。改造思路就是在登录表单上方加一个切换按钮:“密码登录”/“短信登录”。

切到短信登录时,隐藏密码输入框,显示手机号输入框和“获取验证码”按钮。获取验证码按钮点击后调用/api/sms/sendLoginCode接口,60秒倒计时,之后提交时调用/api/sms/loginBySms接口,成功后跳转到原版登录成功跳转的那个URL。

前端核心代码片段:

function sendSmsCode() { var phoneVal = $('#loginPhone').val(); if (!/^1\d{10}$/.test(phoneVal)) { alert('请输入正确的手机号'); return; } $.post('/api/sms/sendLoginCode', {phone: phoneVal}, function(res) { if (res.code === 200) { // 倒计时按钮 var timeLeft = 60; var tipBtn = $('#sendCodeBtn'); tipBtn.attr('disabled', true); var timer = setInterval(function() { tipBtn.text(timeLeft + '秒后重发'); timeLeft--; if (timeLeft <= 0) { clearInterval(timer); tipBtn.text('获取验证码'); tipBtn.attr('disabled', false); } }, 1000); } else { alert(res.msg); } }); } function smsLogin() { var phoneVal = $('#loginPhone').val(); var codeVal = $('#loginCode').val(); $.post('/api/sms/loginBySms', {phone: phoneVal, code: codeVal}, function(res) { if (res.code === 200) { window.location.href = '/'; // 原版登录成功后的跳转地址 } else { alert(res.msg); } }); }

前端注意一个小细节:短信验证码输入框的autocomplete属性建议设置成new-password,防止浏览器自动填充历史验证码,特别是在公用电脑上,这个细节能避免不少尴尬。

3.7 配置文件补充

把短信服务商密钥等敏感信息加到application.properties或application.yml配置文件里,不要硬编码在Java类中。例如:

sms.provider=aliyun sms.accessKeyId=yourAccessKeyId sms.accessKeySecret=yourAccessKeySecret sms.signName=监控平台 sms.templateCode=SMS_123456789 sms.verify.expireMinutes=5 sms.verify.sendIntervalSeconds=60 sms.verify.dayMaxTimes=10

在Java代码里用@Value注入这些配置。这样以后更换短信服务商或调整策略,只改配置文件,不动代码。

4. 常见问题与排查技巧实录

4.1 验证码发送成功但收不到

这个问题90%出现在短信服务商的签名和模板审核上。先登录短信控制台看发送记录,一般有“发送成功但被运营商拦截”、“签名未通过”、“模板变量异常”等状态。常见原因是模板内容里写了“验证码”三个字,但签名跟模板不匹配。比如你的签名是“WGCLOUD监控”,模板内容是“您的验证码为${code},5分钟内有效”,那没问题。但如果模板里带了“登录”两个字,有些短信平台会要求额外提供“登录场景”的报备说明。

排查顺序建议是:先看服务商后台发送记录 -> 确认签名和模板审核状态 -> 手动在后台发一条测试短信 -> 再回到平台调接口。

4.2 短信接口调用时报签名错误

阿里云等平台的签名错误,基本是时间戳参数和编码问题。用官方SDK的通常不会出这类问题,除非你用的SDK版本太老。另外,服务器系统时间一定要校准,偏差超过几分钟就会触发签名校验失败。我在一台内网服务器上部署时,系统时间慢了两分钟,所有短信请求全报签名错误,查了半天才发现是时间同步问题。所以记得配置NTP时间同步。

4.3 登录成功后跳转返回登录页

如果短信登录成功但跳转之后又回到登录页,说明Session可能没有被正确识别。有一种典型情况:你直接把用户对象放进了Session,但WGCLOUD后续校验的是另一个上下文属性。比如它用Spring Security库,那登录成功需要走SecurityContextHolder,而不是简单Session.setAttribute。这种时候只改登录Controller还不够,需要把原版密码登录成功之后的完整逻辑抄过来。我建议你直接全局搜索addFlashAttribute、SecurityContext、session.setAttribute这些关键字,把登录成功那段代码定位准确后再动手。

4.4 多账号绑定同一手机号怎么处理

我们的实现里是手机号唯一对应一个用户,如果表里出现了多条相同phone的记录,findByPhone查询会报错或者返回不确定记录。所以在功能上线前,要确保phone字段有唯一约束:

ALTER TABLE `user` ADD UNIQUE KEY `uk_phone` (`phone`);

如果历史数据有重复,需要先清理。手机号绑定原则上是一个手机号只允许绑定一个WGCLOUD账号,否则短信登录不知道该登录哪个身份。

4.5 安全加固建议

短信登录虽然方便,但安全防护一定别落下。我建议至少做这几件事:

限制IP发送频率。一个IP超过比如每小时20次短信请求就锁定24小时,防止有人批量刷验证码骚扰别人。验证码输入错误超过5次,这个手机号当天禁止再登录,避免暴力穷举。另外,登录成功之后最好加上操作日志,记录登录方式(密码/短信)、手机号、来源IP、登录时间,方便出安全事件时回溯。

还有一个容易忽略的点:短信验证码接口一定别在未登录状态下暴露发送成功与否的差异。如果手机号未绑定账号,接口返回“该手机号未绑定”,那么攻击者可以通过这个接口批量探测哪些手机号注册过你的平台。建议统一返回“验证码已发送,请查收”,然后再在登录校验时提示账号不存在。安全细节越细,后面越省心。

4.6 升级WGCLOUD版本后代码要跟着改

WGCLOUD更新节奏不算慢,版本升级后登录模块的代码可能已经变化。你本地做过二次开发的,升级时需要重新比对SmsLoginController、用户Mapper、前端登录页这三处。我自己的习惯是保持一份patch文件,每次升级后用diff工具把原版代码和新版代码对比,再应用自己的改动。虽然麻烦,但总比升级完发现短信登录功能静默失效要强。

5. 一些合适的扩展思路

如果你觉得只做短信登录还不够,这套改造框架可以很方便地延伸出其他能力。

比如把短信验证码登录扩展到“忘记密码”功能:用户输入手机号,验证后重置密码。原理完全一样,只需要复用验证码生成和校验逻辑。再比如在WGCLOUD的告警通知里加上短信渠道,实际上很多团队已经在用短信收告警了,这时候你把短信平台接入好,登录和告警可以共用同一个短信通道,资源利用率更高。

另外,如果你对账号安全要求很高,可以考虑把短信登录做成双因子认证的一部分。也就是说,用户先用密码登录,登录时如果开启安全校验,再要求输入短信验证码。这个在WGCLOUD上也能做,只要修改密码登录成功后的跳转逻辑,插入一个验证码校验页面即可。不过说实话,WGCLOUD这种内网监控工具,多数场景部署在内网,密码登录已经够用;短信登录更多是满足老板或者客户“方便快捷”的体验需求。改动前先想清楚需求,不要为炫技而改造。

做一个功能前,先做好范围评估。WGCLOUD的短信登录不是官方开箱即用的功能,但它的开源架构给二次开发留了充足空间。你不需要理解全部源码,只需要抓住登录这条链路,就能以很低的成本把短信验证码登录嵌进去。我在实际改造过两三个版本后最深的体会是:官方不支持的功能,并不代表不合适,关键是找到正确的切入点。认证链路通常就那么几行代码,把Session成功写入的方式复制清楚,剩下的都是调用第三方短信接口的事情。

最后再分享一个小技巧:改造完成后,一定不要急着关机,先做一次完整的回归测试。拿一个绑定手机号的测试账号,走一遍“发送验证码-输入验证码-登录成功-刷新页面-退出登录”全流程,再拿一个没绑定手机号的账号试一下异常分支。短信登录这种功能,出问题一般不会出在主流程,而是出在异常提示、按钮倒计时、Session过期这种边角上。把这些边角打磨好,功能才能真正交付出去。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 6:45:14

deepseek接入continue插件实现代码补全:TaoToken统一Key配置与本地验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华