mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 09:33:55 +08:00
feat: 增加server通用secret过滤器
This commit is contained in:
+3
-18
@@ -1,6 +1,6 @@
|
|||||||
package xiaozhi.modules.config.controller;
|
package xiaozhi.modules.config.controller;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -9,13 +9,10 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.validator.ValidatorUtils;
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
||||||
import xiaozhi.modules.config.service.ConfigService;
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
import xiaozhi.modules.sys.dto.ConfigSecretDTO;
|
|
||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,29 +30,17 @@ public class ConfigController {
|
|||||||
|
|
||||||
@PostMapping("server-base")
|
@PostMapping("server-base")
|
||||||
@Operation(summary = "获取配置")
|
@Operation(summary = "获取配置")
|
||||||
public Result<Object> getConfig(@RequestBody ConfigSecretDTO dto) {
|
public Result<Object> getConfig() {
|
||||||
// 效验数据
|
|
||||||
ValidatorUtils.validateEntity(dto);
|
|
||||||
checkSecret(dto.getSecret());
|
|
||||||
Object config = configService.getConfig(true);
|
Object config = configService.getConfig(true);
|
||||||
return new Result<Object>().ok(config);
|
return new Result<Object>().ok(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("agent-models")
|
@PostMapping("agent-models")
|
||||||
@Operation(summary = "获取智能体模型")
|
@Operation(summary = "获取智能体模型")
|
||||||
public Result<Object> getAgentModels(@RequestBody AgentModelsDTO dto) {
|
public Result<Object> getAgentModels(@Valid @RequestBody AgentModelsDTO dto) {
|
||||||
// 效验数据
|
// 效验数据
|
||||||
ValidatorUtils.validateEntity(dto);
|
ValidatorUtils.validateEntity(dto);
|
||||||
checkSecret(dto.getSecret());
|
|
||||||
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
||||||
return new Result<Object>().ok(models);
|
return new Result<Object>().ok(models);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSecret(String secret) {
|
|
||||||
String secretParam = sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
|
||||||
// 验证密钥
|
|
||||||
if (StringUtils.isBlank(secret) || !secret.equals(secretParam)) {
|
|
||||||
throw new RenException("密钥错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package xiaozhi.modules.security.config;
|
package xiaozhi.modules.security.config;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import jakarta.servlet.Filter;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
import org.apache.shiro.session.mgt.SessionManager;
|
import org.apache.shiro.session.mgt.SessionManager;
|
||||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
@@ -14,10 +11,14 @@ import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
|||||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
|
||||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||||
|
import xiaozhi.modules.security.secret.ServerSecretFilter;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shiro的配置文件
|
* Shiro的配置文件
|
||||||
@@ -46,7 +47,7 @@ public class ShiroConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean("shiroFilter")
|
@Bean("shiroFilter")
|
||||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
|
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
|
||||||
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
||||||
config.setFilterOncePerRequest(true);
|
config.setFilterOncePerRequest(true);
|
||||||
|
|
||||||
@@ -54,9 +55,11 @@ public class ShiroConfig {
|
|||||||
shiroFilter.setSecurityManager(securityManager);
|
shiroFilter.setSecurityManager(securityManager);
|
||||||
shiroFilter.setShiroFilterConfiguration(config);
|
shiroFilter.setShiroFilterConfiguration(config);
|
||||||
|
|
||||||
// oauth过滤
|
|
||||||
Map<String, Filter> filters = new HashMap<>();
|
Map<String, Filter> filters = new HashMap<>();
|
||||||
|
// oauth过滤
|
||||||
filters.put("oauth2", new Oauth2Filter());
|
filters.put("oauth2", new Oauth2Filter());
|
||||||
|
// 服务密钥过滤
|
||||||
|
filters.put("server", new ServerSecretFilter(sysParamsService));
|
||||||
shiroFilter.setFilters(filters);
|
shiroFilter.setFilters(filters);
|
||||||
|
|
||||||
// 添加Shiro的内置过滤器
|
// 添加Shiro的内置过滤器
|
||||||
@@ -79,8 +82,8 @@ public class ShiroConfig {
|
|||||||
filterMap.put("/user/login", "anon");
|
filterMap.put("/user/login", "anon");
|
||||||
filterMap.put("/user/pub-config", "anon");
|
filterMap.put("/user/pub-config", "anon");
|
||||||
filterMap.put("/user/register", "anon");
|
filterMap.put("/user/register", "anon");
|
||||||
filterMap.put("/config/server-base", "anon");
|
// 将config路径使用server服务过滤器
|
||||||
filterMap.put("/config/agent-models", "anon");
|
filterMap.put("/config/**", "server");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||||
|
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package xiaozhi.modules.security.secret;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.utils.HttpContextUtils;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config API 过滤器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ServerSecretFilter extends AuthenticatingFilter {
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ServerSecretToken createToken(ServletRequest request, ServletResponse response) {
|
||||||
|
// 获取请求token
|
||||||
|
String token = getRequestToken((HttpServletRequest) request);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
log.warn("createToken:token is empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ServerSecretToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||||
|
// 对OPTIONS请求放行
|
||||||
|
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||||
|
// 获取token并校验
|
||||||
|
String token = getRequestToken((HttpServletRequest) servletRequest);
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
// token为空,返回401
|
||||||
|
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "Authorization token不能为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证token是否匹配
|
||||||
|
String serverSecret = getServerSecret();
|
||||||
|
if (StringUtils.isBlank(serverSecret) || !serverSecret.equals(token)) {
|
||||||
|
// token无效,返回401
|
||||||
|
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "无效的Authorization token");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送未授权响应
|
||||||
|
*/
|
||||||
|
private void sendUnauthorizedResponse(HttpServletResponse response, String message) {
|
||||||
|
response.setContentType("application/json;charset=utf-8");
|
||||||
|
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED, message));
|
||||||
|
response.getWriter().print(json);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("响应输出失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求的token
|
||||||
|
*/
|
||||||
|
private String getRequestToken(HttpServletRequest httpRequest) {
|
||||||
|
String token = null;
|
||||||
|
// 从header中获取token
|
||||||
|
String authorization = httpRequest.getHeader("Authorization");
|
||||||
|
if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
|
||||||
|
token = authorization.replace("Bearer ", "");
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getServerSecret() {
|
||||||
|
return sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package xiaozhi.modules.security.secret;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config API Token
|
||||||
|
*/
|
||||||
|
public class ServerSecretToken implements AuthenticationToken {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private final String token;
|
||||||
|
|
||||||
|
public ServerSecretToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Schema(description = "配置密钥DTO")
|
|
||||||
public class ConfigSecretDTO {
|
|
||||||
@Schema(description = "密钥")
|
|
||||||
@NotBlank(message = "密钥不能为空")
|
|
||||||
private String secret;
|
|
||||||
}
|
|
||||||
@@ -53,6 +53,7 @@ class ManageApiClient:
|
|||||||
headers={
|
headers={
|
||||||
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
"Authorization": "Bearer " + cls._secret
|
||||||
},
|
},
|
||||||
timeout=cls.config.get("timeout", 30), # 默认超时时间30秒
|
timeout=cls.config.get("timeout", 30), # 默认超时时间30秒
|
||||||
)
|
)
|
||||||
@@ -126,7 +127,7 @@ class ManageApiClient:
|
|||||||
def get_server_config() -> Optional[Dict]:
|
def get_server_config() -> Optional[Dict]:
|
||||||
"""获取服务器基础配置"""
|
"""获取服务器基础配置"""
|
||||||
return ManageApiClient._instance._execute_request(
|
return ManageApiClient._instance._execute_request(
|
||||||
"POST", "/config/server-base", json={"secret": ManageApiClient._secret}
|
"POST", "/config/server-base"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +139,6 @@ def get_agent_models(
|
|||||||
"POST",
|
"POST",
|
||||||
"/config/agent-models",
|
"/config/agent-models",
|
||||||
json={
|
json={
|
||||||
"secret": ManageApiClient._secret,
|
|
||||||
"macAddress": mac_address,
|
"macAddress": mac_address,
|
||||||
"clientId": client_id,
|
"clientId": client_id,
|
||||||
"selectedModule": selected_module,
|
"selectedModule": selected_module,
|
||||||
|
|||||||
Reference in New Issue
Block a user