mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
fix: 为Java项目的OTA接口实现WebSocket认证token生成功能,确保与Python端完全兼容。
This commit is contained in:
@@ -141,6 +141,11 @@ public interface Constant {
|
||||
*/
|
||||
String SERVER_MQTT_SECRET = "server.mqtt_signature_key";
|
||||
|
||||
/**
|
||||
* WebSocket认证开关
|
||||
*/
|
||||
String SERVER_AUTH_ENABLED = "server.auth.enabled";
|
||||
|
||||
/**
|
||||
* 无记忆
|
||||
*/
|
||||
|
||||
+52
-1
@@ -1,6 +1,8 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
@@ -169,7 +171,22 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
DeviceReportRespDTO.Websocket websocket = new DeviceReportRespDTO.Websocket();
|
||||
// 从系统参数获取WebSocket URL,如果未配置则使用默认值
|
||||
String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
||||
websocket.setToken("");
|
||||
|
||||
// 检查是否启用认证并生成token
|
||||
String authEnabled = sysParamsService.getValue(Constant.SERVER_AUTH_ENABLED, false);
|
||||
if ("true".equalsIgnoreCase(authEnabled)) {
|
||||
try {
|
||||
// 生成token
|
||||
String token = generateWebSocketToken(clientId, macAddress);
|
||||
websocket.setToken(token);
|
||||
} catch (Exception e) {
|
||||
log.error("生成WebSocket token失败: {}", e.getMessage());
|
||||
websocket.setToken("");
|
||||
}
|
||||
} else {
|
||||
websocket.setToken("");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) {
|
||||
log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置");
|
||||
wsUrl = "ws://xiaozhi.server.com:8000/xiaozhi/v1/";
|
||||
@@ -494,6 +511,40 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
return Base64.getEncoder().encodeToString(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成WebSocket认证token 遵循Python端AuthManager的实现逻辑:token = signature.timestamp
|
||||
*
|
||||
* @param clientId 客户端ID
|
||||
* @param username 用户名 (通常为deviceId/macAddress)
|
||||
* @return 认证token字符串
|
||||
*/
|
||||
private String generateWebSocketToken(String clientId, String username)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// 从系统参数获取密钥
|
||||
String secretKey = sysParamsService.getValue(Constant.SERVER_SECRET, false);
|
||||
if (StringUtils.isBlank(secretKey)) {
|
||||
throw new IllegalStateException("WebSocket认证密钥未配置(server.secret)");
|
||||
}
|
||||
|
||||
// 获取当前时间戳(秒)
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
|
||||
// 构建签名内容: clientId|username|timestamp
|
||||
String content = String.format("%s|%s|%d", clientId, username, timestamp);
|
||||
|
||||
// 生成HMAC-SHA256签名
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
hmac.init(keySpec);
|
||||
byte[] signature = hmac.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Base64 URL-safe编码签名(去除填充符=)
|
||||
String signatureBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(signature);
|
||||
|
||||
// 返回格式: signature.timestamp
|
||||
return String.format("%s.%d", signatureBase64, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建MQTT配置信息
|
||||
*
|
||||
|
||||
@@ -68,6 +68,7 @@ async def get_config_from_api_async(config):
|
||||
"url": config["manager-api"].get("url", ""),
|
||||
"secret": config["manager-api"].get("secret", ""),
|
||||
}
|
||||
auth_enabled = config_data.get("server", {}).get("auth", {}).get("enabled", False)
|
||||
# server的配置以本地为准
|
||||
if config.get("server"):
|
||||
config_data["server"] = {
|
||||
@@ -77,6 +78,7 @@ async def get_config_from_api_async(config):
|
||||
"vision_explain": config["server"].get("vision_explain", ""),
|
||||
"auth_key": config["server"].get("auth_key", ""),
|
||||
}
|
||||
config_data["server"]["auth"] = {"enabled": auth_enabled}
|
||||
# 如果服务器没有prompt_template,则从本地配置读取
|
||||
if not config_data.get("prompt_template"):
|
||||
config_data["prompt_template"] = config.get("prompt_template")
|
||||
|
||||
Reference in New Issue
Block a user