AI 智能体安全踩坑记:Java 为 OpenClaw 添加权限控制与审计日志实战
发布时间:2026/9/28 4:01:30来源:尧图网络
1. 当 OpenClaw 开始“自己动手”Java 侧的安全边界该怎么画OpenClaw 这类智能体框架最吸引人的地方是它真的能“动手”——读写文件、执行命令、操控浏览器甚至从技能市场装第三方插件。但这也是它最危险的地方默认配置下只要 WebSocket 通道连上了Agent 就拥有了完整的工具调用权限。我见过一个真实场景某团队用 OpenClaw 做自动化运维助手结果某个技能包在后台循环调用文件删除命令差点把日志目录清空。问题不在于 OpenClaw 本身有漏洞而在于接入层缺少权限控制和审计留痕。这篇文章面向的是用 Java 做 OpenClaw 接入的开发者。核心要解决的问题有三个第一WebSocket 通道怎么鉴权不能让任何人都能连上网关第二工具调用怎么做白名单把 Agent 的能力限制在必要范围内第三每一次工具调用怎么落盘审计出事后能回放验证。我会给出可复制的 config.toml 骨架、TaoToken 统一 Key/API 通道配置以及越权拦截和日志回放的完整演示。如果你正在用 Java 对接 OpenClaw或者准备把智能体接入生产环境这篇可以跟着做。2. TaoToken 前置统一 Key 与 API 通道配置在讲权限控制之前先把模型调用通道理清楚。OpenClaw 本身不绑定特定模型它通过配置调用外部 API。如果你用多个模型供应商Key 管理会变得很乱。TaoToken 的作用是提供一个统一的 API 通道把模型调用收敛到一个入口这样权限控制和审计日志只需要在一个地方做。TaoToken 的官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。你需要在控制台创建一个 API Key然后把它配置到 OpenClaw 的 config.toml 里。注意API Key 不要硬编码在代码里用环境变量注入。先看 config.toml 的骨架这是 OpenClaw 网关侧的配置# config.toml - OpenClaw 网关配置骨架 [gateway] host 127.0.0.1 port 18789 # WebSocket 鉴权 tokenJava 侧连接时需要携带 auth_token ${OPENCLAW_GATEWAY_TOKEN} # 允许的来源生产环境不要用 * allowed_origins [http://localhost:8080] [model] # 统一走 TaoToken API 通道 provider openai-compatible base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} # 默认模型可按需切换 default_model claude-sonnet-4-20250514 # 单次会话最大 token 数防止成本失控 max_tokens_per_session 100000 [security] # 默认拒绝所有未明确允许的工具 default_action deny # 策略文件路径 policy_file openclaw-policies.toml # 审计日志开关 audit_enabled true # 敏感参数脱敏 mask_sensitive true [sandbox] # 强制 Agent 在受限环境中执行命令 mode docker # 禁止执行的命令前缀 blocked_commands [rm, dd, mkfs, format, shutdown]对应的环境变量在启动 Java 应用前设置export OPENCLAW_GATEWAY_TOKENyour-gateway-token-here export TAOTOKEN_API_KEYsk-your-taotoken-key这里的关键点是OpenClaw 的网关 token 和 TaoToken 的 API Key 是两套独立的凭证。网关 token 控制谁能连上 WebSocketAPI Key 控制模型调用走哪个通道。两者不要混用也不要互相替代。3. 可复制配置WebSocket 鉴权与工具白名单3.1 WebSocket 通道鉴权OpenClaw 的网关默认监听 ws://127.0.0.1:18789协议是自定义的帧格式主要分三类消息req请求、res响应、event事件推送。Java 侧作为客户端连接时需要在连接 URL 上携带 token或者在握手阶段发送鉴权帧。我推荐用 Spring 的 WebSocketClient 来做因为它的连接管理和重连机制比较成熟。先看连接建立的部分Component public class OpenClawGatewayClient { Value(${openclaw.gateway.url:ws://127.0.0.1:18789}) private String gatewayUrl; Value(${openclaw.gateway.token}) private String gatewayToken; private WebSocketSession session; public void connect() throws Exception { StandardWebSocketClient client new StandardWebSocketClient(); // 在 URL 上携带 tokenOpenClaw 网关会在握手阶段校验 String urlWithToken gatewayUrl ?token gatewayToken; this.session client.execute(new GatewayHandler(), urlWithToken).get(); } public boolean isConnected() { return session ! null session.isOpen(); } }注意token 放在 URL 上只是其中一种方式。更安全的做法是在 WebSocket 握手完成后发送一个鉴权帧让网关验证。OpenClaw 支持两种模式你可以在 config.toml 里配置 auth_mode frame 来启用帧鉴权。帧鉴权的 Java 实现private void sendAuthFrame(WebSocketSession session) throws IOException { ObjectMapper mapper new ObjectMapper(); MapString, Object authFrame Map.of( type, req, method, auth, params, Map.of(token, gatewayToken) ); session.sendMessage(new TextMessage(mapper.writeValueAsString(authFrame))); }3.2 工具调用白名单鉴权解决的是“谁能连”白名单解决的是“连上后能做什么”。OpenClaw 的工具调用事件类型是 agent.tool_callJava 中间层需要拦截这类事件根据策略决定放行、阻断还是转人工审批。策略文件用 TOML 格式和 config.toml 放在同一目录# openclaw-policies.toml - 工具调用白名单策略 # 默认拒绝所有未匹配的操作 [[policies]] action deny tool * reason 默认策略未明确允许的操作一律拒绝 # 允许读取工作目录限制路径和文件大小 [[policies]] action allow tool file.read path_pattern ^/workspace/.* max_size 10485760 # 10MB # 文件写入需要人工审批 [[policies]] action require-approval tool file.write path_pattern ^/workspace/.* approvers [team-leadcompany.com] require_justification true # 命令执行只允许特定命令 [[policies]] action allow tool bash.execute command_pattern ^(ls|cat|grep|find|wc) .* # 浏览器操作限制域名 [[policies]] action allow tool browser.navigate url_pattern ^https://(internal|safe-site)\\.com/.*Java 侧的权限拦截器实现Service public class PermissionInterceptor { private final ListSecurityPolicy policies; public PermissionInterceptor(PolicyLoader policyLoader) { this.policies policyLoader.load(); } public PermissionDecision check(ToolCallRequest request) { // 从最严格的规则开始匹配 for (SecurityPolicy policy : policies) { if (matches(policy, request)) { return evaluate(policy, request); } } // 没有匹配到任何策略默认拒绝 return PermissionDecision.deny(未匹配到任何策略默认拒绝); } private boolean matches(SecurityPolicy policy, ToolCallRequest request) { // 工具名匹配支持通配符 if (!*.equals(policy.getTool()) !policy.getTool().equals(request.getToolName())) { return false; } // 路径匹配 if (policy.getPathPattern() ! null) { String path request.getParameters().get(path); if (path null || !Pattern.matches(policy.getPathPattern(), path)) { return false; } } // 命令匹配 if (policy.getCommandPattern() ! null) { String cmd request.getParameters().get(command); if (cmd null || !Pattern.matches(policy.getCommandPattern(), cmd)) { return false; } } // URL 匹配 if (policy.getUrlPattern() ! null) { String url request.getParameters().get(url); if (url null || !Pattern.matches(policy.getUrlPattern(), url)) { return false; } } return true; } private PermissionDecision evaluate(SecurityPolicy policy, ToolCallRequest request) { return switch (policy.getAction()) { case allow - PermissionDecision.allow(); case deny - PermissionDecision.deny(policy.getReason()); case require-approval - PermissionDecision.requireApproval(policy.getApprovers()); default - PermissionDecision.deny(未知策略动作); }; } }这套设计的核心是最小权限原则不是“允许什么”而是“默认拒绝所有只开必要的口子”。文件操作精确到目录级别命令执行限制到具体命令前缀浏览器操作限制域名白名单。3.3 审计日志落盘权限控制是事前阻断审计日志是事后追溯。OpenClaw 本身会输出会话日志格式是 JSONL存在 ~/.openclaw/agents//sessions/ 目录下。但这些日志是 Agent 视角的不够业务化。我们需要在 Java 中间层再建一套业务审计日志记录谁在什么时间通过什么渠道触发了 AgentAgent 调用了哪些工具参数是什么脱敏后权限决策结果是什么耗时和 Token 消耗是多少。审计日志的 Java 实现Component public class AuditLogger { private static final Logger AUDIT_LOG LoggerFactory.getLogger(OPENCLAW_AUDIT); private final ObjectMapper mapper new ObjectMapper(); public void logToolCall(ToolCallRequest request, PermissionDecision decision, String sessionId, String userId) { try { AuditEntry entry AuditEntry.builder() .timestamp(Instant.now()) .eventType(TOOL_CALL_ATTEMPT) .sessionId(sessionId) .userId(userId) .toolName(request.getToolName()) .parameters(maskSensitiveParams(request.getParameters())) .decision(decision.getAction().name()) .reason(decision.getReason()) .sourceIp(RequestContext.getCurrentIp()) .build(); AUDIT_LOG.info(mapper.writeValueAsString(entry)); } catch (JsonProcessingException e) { AUDIT_LOG.error(审计日志序列化失败, e); } } private MapString, Object maskSensitiveParams(MapString, Object params) { MapString, Object masked new HashMap(params); // 密钥、密码类字段脱敏 for (String key : List.of(api_key, password, token, secret)) { if (masked.containsKey(key)) { masked.put(key, ***MASKED***); } } return masked; } }Logback 配置把审计日志单独输出到文件按天滚动appender nameAUDIT_FILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/openclaw-audit.jsonl/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/openclaw-audit.%d{yyyy-MM-dd}.jsonl.gz/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder pattern%msg%n/pattern /encoder /appender logger nameOPENCLAW_AUDIT levelINFO additivityfalse appender-ref refAUDIT_FILE/ /logger4. 验证请求与成功结果配置写完了得验证是否真的生效。我分三步来测越权拦截、正常放行、日志回放。4.1 越权拦截验证构造一个文件删除请求看策略是否阻断Test public void testFileDeleteBlocked() { ToolCallRequest request ToolCallRequest.builder() .toolName(file.delete) .parameters(Map.of(path, /workspace/important.log)) .build(); PermissionDecision decision permissionInterceptor.check(request); assertThat(decision.isDenied()).isTrue(); assertThat(decision.getReason()).contains(默认策略); }运行结果请求被阻断审计日志里出现一条 TOOL_CALL_ATTEMPT 记录decision 字段是 DENYreason 是“默认策略未明确允许的操作一律拒绝”。同时Java 中间层会向上游 OpenClaw 发送取消指令Agent 收到后停止执行。4.2 正常放行验证构造一个允许的文件读取请求Test public void testFileReadAllowed() { ToolCallRequest request ToolCallRequest.builder() .toolName(file.read) .parameters(Map.of(path, /workspace/config.yaml)) .build(); PermissionDecision decision permissionInterceptor.check(request); assertThat(decision.isAllowed()).isTrue(); }运行结果请求放行审计日志记录 decision 为 ALLOW参数中的 path 完整记录没有脱敏因为不是敏感字段。4.3 日志回放验证审计日志落盘后可以用命令行工具回放# 查看今天的审计日志 cat logs/openclaw-audit.jsonl | jq . # 筛选所有被阻断的请求 cat logs/openclaw-audit.jsonl | jq select(.decision DENY) # 统计每个工具被调用的次数 cat logs/openclaw-audit.jsonl | jq -r .toolName | sort | uniq -c | sort -rn回放结果能清晰看到哪个用户、在哪个会话、调用了什么工具、参数是什么、决策结果是什么。如果出现异常调用可以快速定位。5. 本篇常见错排查5.1 WebSocket 连接被拒绝报错信息Connection refused: ws://127.0.0.1:18789排查步骤先确认 OpenClaw 网关是否启动用curl http://127.0.0.1:18789/health检查健康端点。如果网关没启动检查 config.toml 里的 host 和 port 配置。如果网关启动了但连接被拒检查 auth_token 是否匹配以及 allowed_origins 是否包含了你的 Java 应用地址。5.2 工具调用事件识别失败报错信息isToolCallEvent returned false for payload原因通常是 OpenClaw 版本升级后事件格式变了。OpenClaw 的工具调用事件类型是 agent.tool_call但不同版本可能在 JSON 结构上有差异。建议不要用字符串包含来判断而是用 JSON 解析private boolean isToolCallEvent(String payload) { try { JsonNode node mapper.readTree(payload); return event.equals(node.path(type).asText()) agent.tool_call.equals(node.path(event).asText()); } catch (JsonProcessingException e) { return false; } }5.3 审计日志写入失败报错信息Failed to serialize audit entry常见原因是参数里包含了不可序列化的对象比如 InputStream 或自定义类。解决方案是在写入前做一次参数清洗把所有非基本类型转成字符串private MapString, Object sanitizeParams(MapString, Object params) { MapString, Object sanitized new HashMap(); for (Map.EntryString, Object entry : params.entrySet()) { Object value entry.getValue(); if (value instanceof String || value instanceof Number || value instanceof Boolean || value null) { sanitized.put(entry.getKey(), value); } else { sanitized.put(entry.getKey(), value.toString()); } } return sanitized; }5.4 权限策略不生效现象明明配置了 deny 策略但请求还是被放行了。排查检查策略文件的加载顺序。策略是按顺序匹配的第一条匹配到的规则生效。如果你把 allow 规则放在了 deny 规则前面allow 会先匹配。建议把最严格的规则放在最前面或者用 default_action deny 作为兜底。5.5 TaoToken API 调用返回 401报错信息401 Unauthorized from https://taotoken.net/api检查环境变量 TAOTOKEN_API_KEY 是否设置正确以及 Key 是否在控制台被禁用。另外注意API Key 不要有多余的空格或换行。可以在 Java 启动时打印一下 Key 的前后各 4 位确认没有截断。6. 接入与排障的下一步如果你在接入过程中遇到权限策略不生效、审计日志格式不对、或者 WebSocket 连接不稳定的问题建议先去 TaoToken 控制台确认 API Key 的状态和额度然后对照接入文档检查配置项。排障和接入相关的操作可以从 API Keys 页面和接入文档入手API Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你需要验证模型调用是否正常可以用模型对话页面快速测试模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你打算长期用 OpenClaw 做编码或 Agent 任务建议配置 Coding Plan把模型调用和权限控制统一管理Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite最后提醒一点权限控制和审计日志不是一次配置就完事的。OpenClaw 的技能市场会不断更新新的工具调用类型可能出现。建议每周检查一次审计日志看看有没有未匹配到策略的调用及时补充白名单规则。这样你的“龙虾”才能既好用又安全。
网站建设高端定制企业官网