From 37d6781e6a1e0a748e2041ad9469e0de095917d6 Mon Sep 17 00:00:00 2001 From: gx_ma <1773945958@qq.com> Date: Tue, 3 Nov 2026 16:42:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=AE=89=E5=85=A8):=20MQTT=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=9B=9E=E8=B0=83=E5=BC=BA=E5=88=B6=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=80=81=E5=B9=B6=E5=A4=96=E7=BD=AEJWT?= =?UTF-8?q?=E5=AF=86=E9=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 #31:/iot/tool/mqtt/auth 与 /authv5 对 web/phone 客户端仅验签名即放行, 现要求 JWT 有效且 Redis 存在活跃会话、用户状态正常(与内置 broker 一致), 伪造 token 无会话即被拒;token.secret 支持经 TOKEN_SECRET 注入唯一值。 --- .../src/main/resources/application.yml | 2 +- .../data/controller/ToolController.java | 46 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/springboot/fastbee-admin/src/main/resources/application.yml b/springboot/fastbee-admin/src/main/resources/application.yml index 88294986..6dbceee1 100644 --- a/springboot/fastbee-admin/src/main/resources/application.yml +++ b/springboot/fastbee-admin/src/main/resources/application.yml @@ -77,7 +77,7 @@ user: # token配置 token: header: Authorization # 令牌自定义标识 - secret: abcdefghijklfastbeesmartrstuvwxyz # 令牌密钥 + secret: ${TOKEN_SECRET:abcdefghijklfastbeesmartrstuvwxyz} # 令牌密钥(生产请通过环境变量 TOKEN_SECRET 注入唯一高强度值) expireTime: 1440 # 令牌有效期(默认30分钟)1440为一天 # mybatis-plus配置 diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java index 2639a495..ef682fc5 100644 --- a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java @@ -4,12 +4,15 @@ import com.alibaba.fastjson2.JSONObject; import com.fastbee.common.ProtocolDeCodeService; import com.fastbee.common.annotation.Log; import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.CacheConstants; import com.fastbee.common.constant.Constants; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; import com.fastbee.common.core.iot.response.DeCodeBo; import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.BusinessType; import com.fastbee.common.exception.file.FileNameLengthLimitExceededException; import com.fastbee.common.utils.MessageUtils; @@ -84,6 +87,9 @@ public class ToolController extends BaseController { @Value("${token.secret}") private String secret; + @Resource + private RedisCache redisCache; + @Resource private ProtocolDeCodeService deCodeService; @Resource @@ -111,6 +117,27 @@ public class ToolController extends BaseController { return getDataTable(list); } + /** + * Web/移动端 MQTT 认证:JWT 签名合法还不够,必须回查登录态与用户状态 + */ + private boolean isValidWebSession(String token) { + try { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + if (StringUtils.isEmpty(uuid)) { + return false; + } + LoginUser loginUser = redisCache.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + uuid); + if (loginUser == null || loginUser.getUser() == null) { + return false; + } + SysUser user = loginUser.getUser(); + return "0".equals(user.getStatus()) && !"2".equals(user.getDelFlag()); + } catch (Exception ex) { + return false; + } + } + @ApiOperation("mqtt认证") @PostMapping("/mqtt/auth") public ResponseEntity mqttAuth(@RequestParam String clientid, @RequestParam String username, @RequestParam String password) throws Exception { @@ -123,18 +150,17 @@ public class ToolController extends BaseController { return toolService.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), MessageUtils.message("mqtt.unauthorized")); } } else if (clientid.startsWith("web") || clientid.startsWith("phone")) { - // web端和移动端认证:token认证 + // web端和移动端认证:token有效且存在活跃登录态 String token = password; if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { token = token.replace(Constants.TOKEN_PREFIX, ""); } - try { - Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + if (isValidWebSession(token)) { log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientid + "---------------"); return ResponseEntity.ok().body("ok"); - } catch (Exception ex) { - return toolService.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), ex.getMessage()); } + return toolService.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), + "移动端/Web端mqtt认证失败:登录态已失效或用户不可用"); } else { // 替换为整合之后的接口 return toolService.clientAuth(clientid, username, password); @@ -161,20 +187,18 @@ public class ToolController extends BaseController { return ResponseEntity.ok().body(ret); } } else if (clientid.startsWith("web") || clientid.startsWith("phone")) { - // web端和移动端认证:token认证 + // web端和移动端认证:token有效且存在活跃登录态 String token = password; if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { token = token.replace(Constants.TOKEN_PREFIX, ""); } - try { - Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + if (isValidWebSession(token)) { log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientid + "---------------"); ret.put("result", "allow"); - return ResponseEntity.ok().body(ret); - } catch (Exception ex) { + } else { ret.put("result", "deny"); - return ResponseEntity.ok().body(ret); } + return ResponseEntity.ok().body(ret); } else { // 替换为整合之后的接口 return toolService.clientAuthv5(clientid, username, password);