# sn_agent **Repository Path**: junjungithome/sn_agent ## Basic Information - **Project Name**: sn_agent - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-01 - **Last Updated**: 2026-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sn-agent 基于 **AK/SK 鉴权** 的轻量 API 网关:外部系统携带签名访问网关,网关完成鉴权后,把请求 **原样透明转发** 到后端服务(如 LIS、HIS 等业务系统),并把后端响应原样回传。 - 鉴权:HMAC-SHA256 签名 + 时间窗 + nonce 防重放 + IP 白名单 + 路由级 ACL,**支持按路由/按路径免鉴权** - 转发:不解析、不改写请求体与响应体,**支持任意 Content-Type**(JSON / XML / form / 文本 / 二进制…) - **管理控制台**:登录后可页面化配置路由、给不同用户分配不同 AK/SK、查看详细请求日志 - 存储:Oracle(生产)/ H2(本地开发、测试) - 治理:全链路 `requestId`、审计日志异步落库、路由热加载、连接池与路由级超时 --- ## 管理控制台(Web) 启动后浏览器访问 ,**必须登录**: | 项 | 值 | |---|---| | 默认账号 | `admin` | | 默认密码 | `SNadmin@123` | > 默认账号仅在库中无管理员时自动初始化一次,密码以 BCrypt 密文存储。 > 登录后请通过右上角「修改密码」立即修改。可通过环境变量覆盖初始账号: > `SNAG_ADMIN_USER` / `SNAG_ADMIN_PASSWORD`。 ### 三个功能页 | 页面 | 能力 | |---|---| | **路由管理** | 网关路径 ⇄ 后端地址的增删改查、允许的 HTTP 方法、路由级超时、**是否需要 AK/SK 鉴权**、**连通性测试**、启停、一键刷新路由表 | | **密钥管理** | 为每个接入方创建独立 AK/SK(可自动生成)、勾选「授权路由」、IP 白名单、有效期、启停、重置 SK、删除 | | **请求日志** | 近 24h 调用概览(总量/成功/失败/成功率);按 AK、路由、方法、状态码、成功失败、路径关键字、请求 ID、客户端 IP、时间范围筛选;分页查看;**查看完整详情** | ### 连通性测试 在路由管理页: - 点顶部 **「连通性测试」** 自由输入路径测试 - 或点某一行的 **「测试」** 直接带入该路由的网关路径(通配路径自动转成可测试的示例路径) **两种模式**: | 模式 | 说明 | 适用场景 | |---|---|---| | **直连后端** | 网关直接请求后端地址,跳过鉴权与网关链路 | 只想确认「后端通不通」 | | **经网关** | 网关回环请求自身代理入口,完整走「匹配 → 鉴权 → 转发」 | 验证签名、路由授权、转发是否都正确 | 表单支持:HTTP 方法、Query String、请求体、Content-Type、自定义超时、选择密钥(需鉴权路由在经网关模式下会自动用该 SK 生成合法签名)。 测试结果会展示:结论、匹配到的路由、实际请求地址、鉴权情况、状态码、耗时、响应类型/大小、响应头、响应体预览(超 4096 字符截断), 失败时附带**诊断建议**,例如: | 现象 | 建议 | |---|---| | 未匹配到路由 | 检查该路径是否被某条路由匹配;注意精确路径优先于通配路径 | | 401 | 检查 AK 是否已授权此路由、SK 是否正确、时间戳是否在窗口内 | | 403 | AK 未授权此路由,或来源 IP 不在白名单 | | 404 | 检查「后端地址 + 后端路径」拼接后的完整 URL | | 连接超时 | 检查后端是否存活、网络可达,或调大读取超时 | | 无法连接 | 检查后端地址端口、防火墙、服务是否已启动 | | 域名无法解析 | 检查主机名拼写与 DNS | > 超时上限为 120s,单次测试请求体上限 2MB,避免测试本身拖垮网关。 ### 「一个接口、多个用户、不同 AK/SK」 路由与密钥是多对多关系(`SNAG_AK_ROUTE`): ``` ┌── 密钥A(XX医院) ──┐ 路由 /xx/yyy ──┼── 密钥B(YY厂商) ──┤ 各用各自的 SK 签名,互不影响 └── 密钥C(测试) ──┘ ``` 操作步骤:密钥管理 → 新建/编辑密钥 → 勾选「授权路由」。 **未授权的路由一律返回 403 `ROUTE_FORBIDDEN`**;可随时取消勾选单独停用某个用户的某个接口。 ### 日志详情记录的字段 `请求ID / 时间 / 方法 / 状态码 / AK / 路由 / 网关耗时 / 上游耗时 / 客户端IP / 请求大小 / 响应大小 / 结果` 以及 **请求路径、目标地址、Query、User-Agent、错误码、错误信息、请求头、请求体、响应头、响应体**。 - 报文体**仅记录文本类** Content-Type(JSON/XML/文本/form 等,见 `audit.capture-content-types`), 二进制(图片、压缩包、octet-stream 等)不落库,避免污染存储 - 单条报文体默认截断到 8192 字符(`audit.max-body-length`) - 敏感头自动脱敏:`X-Auth-Signature`、`X-Auth-Nonce`、`X-Auth-Timestamp`、`Authorization`、`Cookie` 等记录为 `******` ### 控制台安全说明 - 安全过滤链**只作用于** `/admin/**`、`/login`、`/logout`,代理路径完全不受影响 - 管理台写操作启用 **CSRF** 防护;会话默认 1 小时超时 - 管理台路径已加入网关排除列表(`proxy.exclude-paths`),不会被误当成路由转发 --- ## 一、整体流程 ``` Client │ ① 携带 X-Auth-Ak / X-Auth-Timestamp / X-Auth-Nonce / X-Auth-Signature ▼ TraceIdFilter 生成/透传 X-Request-Id ▼ GatewayFilter 排除自有端点 → 匹配路由(未匹配则放行)→ 缓存请求体 ├─ 鉴权 时间窗 → nonce → 验签 → IP 白名单 → 路由 ACL ├─ 转发 拼装目标 URL(原样透传 path/query)→ 转发 ├─ 回写 状态码 / 响应头白名单 / 响应体 原样回写 └─ 审计 异步落库(含请求响应头与报文体) ▼ Backends http://192.168.199.128:9903/lis-core-webapp/rest/v1/... ``` 管理台请求(`/admin/**`、`/login`)由 Spring Security 保护,不进入上述网关链路。 目录结构: ``` src/main/java/org/example/snagent/ ├── config/ AgentProperties、RestClientProvider、SecurityConfig(登录)、WebConfig、AsyncConfig ├── support/ SignUtil(签名算法)、CachedBodyHttpServletRequest、HeaderUtils、BodyCapture、ErrorCode ├── auth/ AkSkAuthenticator(验签)、NonceStore(防重放)、AppKeyService ├── route/ RouteRegistry(路由表+热加载)、RouteMatch(目标地址解析) ├── gateway/ GatewayFilter(路由 → 鉴权 → 转发 → 回写 → 审计,全链路接管) ├── proxy/ ForwardRequest / ForwardResult / ForwardingService(透明转发) ├── audit/ AuditService(异步落库)、AuditQueryService(查询与统计) ├── admin/ RouteAdminService、AppKeyAdminService、RouteTestService(连通性测试)、 │ AdminUserDetailsService + web 控制器与 DTO ├── domain/ AppKey / RouteRule / AkRoute / AuditLog / AdminUser 实体与 Repository └── web/ GlobalExceptionHandler、EchoController(自检接口) src/main/resources/ ├── templates/admin/ login、routes、keys、logs(Thymeleaf) ├── static/assets/ app.css、app.js(纯本地,无 CDN 依赖) └── application.yml、application-oracle.yml ``` --- ## 二、签名协议 ### 1. 请求头 | 头 | 说明 | |---|---| | `X-Auth-Ak` | 访问密钥 ID,如 `demo_ak_2024` | | `X-Auth-Timestamp` | 毫秒时间戳,与网关时间偏差需在 ±300s 内 | | `X-Auth-Nonce` | 随机串,同一 AK 下窗口内不可重复 | | `X-Auth-Signature` | `Base64(HMAC-SHA256(SK, CanonicalString))` | 也支持单头形式(二选一即可): ```http Authorization: SN-HMAC-SHA256 ak=,ts=<毫秒时间戳>,nonce=<随机串>,sign=<签名> ``` ### 2. 待签串 CanonicalString ``` HTTPMethod \n Path \n CanonicalQuery \n AK \n Timestamp \n Nonce \n BodyHash ``` - 各段以 `\n` 连接,共 7 段,最后一段无换行 - `Path`:原始请求路径(含 `/api` 前缀,不解码),如 `/api/lis/xmg/sentBcToTM19` - `CanonicalQuery`:`k=v` 按字典序升序排列后以 `&` 连接,**不做二次编解码**;无 query 时为空串 - `BodyHash`:`HEX(SHA-256(请求体字节))`,无 body 时用空串摘要 (`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`) - 签名:`Base64(HMAC-SHA256(SK, CanonicalString))` > 服务端与客户端**必须按完全相同的字节**参与签名。请求体请勿在签名后二次修改。 ### 3. 校验顺序(短路) `AK 存在 → 未禁用 → 未过期 → 时间窗 → nonce 未重放 → 签名一致 → IP 白名单 → 路由 ACL` 任一失败即返回对应错误码,不进入转发。 --- ## 三、快速开始 ### 1. 本地启动(H2,开箱即用) ```bash mvn clean package -DskipTests java -jar target/sn-agent.jar ``` 启动时自动建表并写入演示数据(`data-dev.sql`): | 项 | 值 | |---|---| | AK | `demo_ak_2024` | | SK | `demo_sk_2024_secret` | | 路由 | `/api/lis/**` → `http://192.168.199.128:9903/lis-core-webapp/rest/v1/lis/{remaining}` | | 路由 | `/api/echo/**` → 本机 `/_agent/echo`(回环自检,端口与 `server.port` 一致) | 冒烟验证: ```bash chmod +x scripts/agent-call.sh # GET(回环到自身 echo 接口) ./scripts/agent-call.sh GET /api/echo/demo # POST JSON echo -n '{"patient":"张三","age":30}' > /tmp/p.json ./scripts/agent-call.sh POST /api/echo/demo /tmp/p.json application/json # POST XML echo -n '李四' > /tmp/p.xml ./scripts/agent-call.sh POST /api/echo/demo /tmp/p.xml application/xml ``` > 演示路由 `/api/lis/**` 指向示例内网地址,无法连通时会返回 `502/504`,属正常现象。 ### 2. Oracle 启动(生产) ```bash # 1) 建表(首次,含 SNAG_ADMIN_USER 管理账号表) sqlplus sn_agent/密码 @db/oracle/schema.sql sqlplus sn_agent/密码 @db/oracle/sample-data.sql # 可选示例数据(不含管理员账号,由程序自动初始化) # 2) 启动 java -jar target/sn-agent.jar --spring.profiles.active=oracle \ --SNAG_DB_URL=jdbc:oracle:thin:@//192.168.1.10:1521/ORCLPDB1 \ --SNAG_DB_USER=sn_agent --SNAG_DB_PASSWORD=xxxxxx ``` 生产环境请把 `sn-agent.echo.enabled` 设为 `false`,并重置示例 SK。 --- ## 四、配置项(`sn-agent.*`) | 配置 | 默认值 | 说明 | |---|---|---| | `security.enabled` | `true` | 全局 AK/SK 鉴权开关,关闭后所有路由免鉴权 | | `security.skip-auth-paths` | 空 | 免鉴权路径白名单(Ant 风格网关路径),命中即放行,优先级高于路由配置 | | `security.header-prefix` | `X-Auth-` | 鉴权头前缀 | | `security.time-window-seconds` | `300` | 时间戳有效窗口(双向) | | `security.max-body-size-bytes` | `10485760` | 请求体缓存上限,超出返回 413 | | `security.forward-authorization` | `false` | 是否透传客户端 `Authorization` 头 | | `proxy.path-prefix` | 空 | 代理入口前缀;**为空表示除排除路径外的任意路径都可配置为路由**(如 `/xx/yyy`),非空时(如 `/api`)仅该前缀下生效 | | `proxy.exclude-paths` | `/admin/**`、`/_agent/**`、`/actuator/**`、`/assets/**`、`/login`、`/logout`、`/favicon.ico`、`/error` | 不进入网关的自有端点 | | `proxy.connect-timeout-ms` | `3000` | 默认连接超时 | | `proxy.read-timeout-ms` | `30000` | 默认读取超时 | | `proxy.add-forwarded-headers` | `true` | 追加 `X-Forwarded-For/Proto/Host`、`X-Request-Id`、`X-Forwarded-Ak` | | `route.reload-interval-ms` | `60000` | 路由表热刷新间隔,0 表示不自动刷新 | | `http.max-conn-total` / `max-conn-per-route` | `200` / `50` | 转发连接池 | | `audit.enabled` | `true` | 审计日志开关 | | `audit.capture-body` | `true` | 是否记录请求/响应报文体 | | `audit.capture-headers` | `true` | 是否记录请求/响应头(敏感头自动脱敏) | | `audit.max-body-length` | `8192` | 单条报文体最大记录字符数 | | `audit.capture-content-types` | `text/*`、`application/json`、`application/*+json`、`application/xml`、`application/*+xml`、`application/x-www-form-urlencoded` 等 | 记录报文体的 Content-Type 白名单 | | `audit.retain-days` | `0` | 日志保留天数,0 表示不清理(>0 时每天 03:00 自动清理) | | `admin.default-username` / `default-password` | `admin` / `SNadmin@123` | 初始化管理员账号(仅首次) | | `admin.page-size` | `20` | 日志页单页条数 | | `echo.enabled` | `true` | 自检接口 `/_agent/echo` 开关 | --- ## 五、路由与授权(表配置) ### SNAG_APP_KEY(应用密钥) | 字段 | 说明 | |---|---| | `AK_CODE` | 访问密钥 ID,唯一 | | `SK_CODE` | 秘密访问密钥(生产建议加密存储或接入 KMS) | | `ENABLED` / `EXPIRES_AT` | 启停与有效期 | | `IP_WHITE_LIST` | IP 白名单,逗号分隔,支持 `192.168.1.*`,空表示不限制 | ### SNAG_ROUTE_RULE(路由规则) | 字段 | 说明 | |---|---| | `ROUTE_ID` | 路由唯一标识 | | `PATH_PATTERN` | 网关侧路径(Ant 风格,含 `/api` 前缀),如 `/api/lis/**`、`/api/order/{id}` | | `METHODS` | 允许的 HTTP 方法,逗号分隔,空表示不限制 | | `TARGET_BASE_URL` | 后端基地址,如 `http://192.168.199.128:9903` | | `TARGET_PATH` | 后端路径,支持占位符;**为空则把剩余路径拼到 baseUrl 之后** | | `AUTH_REQUIRED` | **是否需要 AK/SK 鉴权**,`0`=免鉴权公开接口,`1`=需鉴权(默认) | | `CONNECT_TIMEOUT_MS` / `READ_TIMEOUT_MS` | 路由级超时,为空取全局默认值 | 精确路径优先于通配路径匹配。示例: ```sql INSERT INTO SNAG_ROUTE_RULE (ID, ROUTE_ID, PATH_PATTERN, METHODS, TARGET_BASE_URL, TARGET_PATH, CONNECT_TIMEOUT_MS, READ_TIMEOUT_MS, ENABLED, DESCRIPTION, CREATED_AT) VALUES (10, 'lis-sent-bc', '/api/lis/**', 'GET,POST', 'http://192.168.199.128:9903', '/lis-core-webapp/rest/v1/lis/{remaining}', 3000, 30000, 1, 'LIS 检验接口转发', SYSTIMESTAMP); ``` **`TARGET_PATH` 占位符规则**: | 写法 | 含义 | 要求 | |---|---|---| | `{var}` | 单段路径变量,如 `PATH_PATTERN=/api/order/{id}` | 变量名必须定义在 `PATH_PATTERN` 中 | | `{remaining}` | pattern 匹配后的**剩余多段路径**,如 `/api/lis/xmg/sentBcToTM19` → `xmg/sentBcToTM19` | 任意 pattern,推荐配合 `**` 使用 | | 留空 | 剩余路径直接拼到 `TARGET_BASE_URL` 之后 | 任意 pattern | > 注意:底层为 `AntPathMatcher`,**不支持** `{**tail}` 这类「多段 + 命名捕获」写法, > 多段剩余路径请使用 `{remaining}` 或留空 `TARGET_PATH`。 > 占位符无法解析时,网关会打印 WARN 并原样拼接(表现为后端收到 `%7Btail%7D`)。 ### 免鉴权接口(部分接口不需要 AK/SK) 三级开关,**优先级从高到低**: | 级别 | 配置方式 | 说明 | |---|---|---| | 1. 全局 | `sn-agent.security.enabled: false` | 关闭后**所有**路由免鉴权,仅建议内网可信环境/联调使用 | | 2. 路径白名单 | `sn-agent.security.skip-auth-paths` | 命中路径即放行,**优先级高于路由配置** | | 3. 单条路由 | 路由表 `AUTH_REQUIRED = 0` 或管理台取消勾选「需要 AK/SK 鉴权」 | **最常用**,精确到单个接口 | **推荐做法**:管理台 → 路由管理 → 编辑路由 → 取消勾选「需要 AK/SK 鉴权」。列表页会显示「免鉴权」黄色标签。 YAML 路径白名单示例: ```yaml sn-agent: security: skip-auth-paths: - /api/public/** - /api/health ``` SQL 示例: ```sql -- 新增免鉴权路由 INSERT INTO SNAG_ROUTE_RULE (ID, ROUTE_ID, PATH_PATTERN, METHODS, TARGET_BASE_URL, TARGET_PATH, ENABLED, AUTH_REQUIRED, DESCRIPTION, CREATED_AT) VALUES (20, 'public-dict', '/api/public/dict/**', 'GET', 'http://192.168.199.128:9903', '/lis-core-webapp/rest/v1/dict/{remaining}', 1, 0, '公开字典查询(免鉴权)', SYSTIMESTAMP); ``` > **行为说明**:免鉴权路由不做签名校验,但仍会正常转发、记录审计日志(审计日志 `AK_CODE` 为空), > 且**不会**在转发时带上 `X-Forwarded-Ak` 头。免鉴权路由无需在 `SNAG_AK_ROUTE` 中绑定。 > > **安全提示**:免鉴权意味着任何人可直接打到后端,请确保后端接口本身是公开安全的,并考虑配合 IP 白名单。 ### SNAG_AK_ROUTE(路由 ACL) 授权后 AK 才能访问对应路由,**未授权一律 403**: ```sql INSERT INTO SNAG_AK_ROUTE (ID, AK_ID, ROUTE_RULE_ID, ENABLED, CREATED_AT) VALUES (10, , 10, 1, SYSTIMESTAMP); ``` > 修改路由表后等待 `route.reload-interval-ms`(默认 60s)自动生效, > 也可在「路由管理」页点「刷新路由表」立即生效(管理台保存路由时会自动刷新)。 > > 网关路径**不再限定 `/api` 前缀**:`sn-agent.proxy.path-prefix` 默认为空, > 除 `proxy.exclude-paths` 列出的自有端点外,任意路径(如 `/xx/yyy`)都可配置为路由。 > 若只想代理某一前缀,把该项设为 `/api` 即可。 --- ## 六、转发行为 | 项 | 行为 | |---|---| | 请求体 | 原样字节转发,不解析、不重编码,支持 JSON / XML / form / 文本 / 二进制 | | Query | 原样透传(不重排、不重编码) | | 请求头 | 过滤 `Host`、`Connection`、`Content-Length`、`Accept-Encoding` 等;剥离客户端伪造的 `X-Forwarded-*` 与网关鉴权头 `X-Auth-*`;追加真实 `X-Forwarded-For` | | 响应状态码 | 原样透传(含 4xx/5xx,网关不改写) | | 响应头 | 白名单透传(`Content-*`、`Cache-Control`、`ETag`、`Location`、`Set-Cookie` 等)+ 全部 `X-*` 自定义头;剔除 `Transfer-Encoding`、`Content-Length`(按实际长度重算) | | 重定向 | 网关不跟随,3xx 原样返回客户端 | | 重试/熔断 | 不重试(幂等性由调用方保证),后端故障直接映射为 502/504 | 错误响应统一格式: ```json { "code": "SIGNATURE_MISMATCH", "message": "签名校验失败", "requestId": "3e6120d8-e18e-4fd6-87cc-fd14ac1f2042", "path": "/api/lis/xmg/sentBcToTM19", "timestamp": 1788255214072 } ``` 错误码:`MISSING_AUTH_HEADER(401)`、`AK_NOT_FOUND(401)`、`AK_DISABLED(403)`、`AK_EXPIRED(403)`、 `REQUEST_EXPIRED(401)`、`NONCE_REUSED(401)`、`SIGNATURE_MISMATCH(401)`、`IP_NOT_ALLOWED(403)`、 `BODY_TOO_LARGE(413)`、`ROUTE_NOT_FOUND(404)`、`METHOD_NOT_ALLOWED(405)`、`ROUTE_FORBIDDEN(403)`、 `UPSTREAM_TIMEOUT(504)`、`UPSTREAM_UNREACHABLE(502)`、`UPSTREAM_ERROR(502)`。 --- ## 七、审计日志(SNAG_AUDIT_LOG) 异步落库,不阻塞转发链路;队列满时丢弃最旧记录并告警。记录字段: | 分类 | 字段 | |---|---| | 链路 | `REQUEST_ID`、`AK_CODE`、`ROUTE_ID`、`ROUTE_NAME`、`CREATED_AT` | | 请求 | `HTTP_METHOD`、`REQ_PATH`、`TARGET_URL`、`QUERY_STRING`、`REQ_CONTENT_TYPE`、`REQ_SIZE`、`REQ_HEADERS`、`REQ_BODY` | | 来源 | `CLIENT_IP`、`USER_AGENT` | | 响应 | `RESP_STATUS`、`RESP_CONTENT_TYPE`、`RESP_SIZE`、`RESP_HEADERS`、`RESP_BODY` | | 结果 | `COST_MS`、`UPSTREAM_COST_MS`、`SUCCESS_FLAG`、`ERROR_CODE`、`ERROR_MSG`、`TRUNCATED_FLAG` | `REQ_HEADERS`/`RESP_HEADERS` 为 JSON 文本,`REQ_BODY`/`RESP_BODY` 为 CLOB。 配置 `audit.retain-days` > 0 可开启每天 03:00 的自动清理。 --- ## 八、客户端接入示例 ### Java(复用 `SignUtil`) ```java String ak = "demo_ak_2024"; String sk = "demo_sk_2024_secret"; long ts = System.currentTimeMillis(); String nonce = UUID.randomUUID().toString().replace("-", ""); byte[] body = "{\"patient\":\"张三\"}".getBytes(StandardCharsets.UTF_8); String signature = SignUtil.sign(sk, "POST", "/api/lis/xmg/sentBcToTM19", "", ak, String.valueOf(ts), nonce, body); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("X-Auth-Ak", ak); headers.set("X-Auth-Timestamp", String.valueOf(ts)); headers.set("X-Auth-Nonce", nonce); headers.set("X-Auth-Signature", signature); // restTemplate / restClient 发送即可 ``` ### Shell 见 `scripts/agent-call.sh`(`openssl dgst -sha256 -hmac` 实现签名)。 ### Python ```python import hashlib, hmac, base64, time, uuid, requests ak, sk = "demo_ak_2024", "demo_sk_2024_secret" body = b'{"patient":"\xe5\xbc\xa0\xe4\xb8\x89"}' path, method = "/api/lis/xmg/sentBcToTM19", "POST" ts, nonce = str(int(time.time() * 1000)), uuid.uuid4().hex canonical = "\n".join([method, path, "", ak, ts, nonce, hashlib.sha256(body).hexdigest()]) sign = base64.b64encode(hmac.new(sk.encode(), canonical.encode(), hashlib.sha256).digest()).decode() requests.post("http://127.0.0.1:8080" + path, data=body, headers={"Content-Type": "application/json", "X-Auth-Ak": ak, "X-Auth-Timestamp": ts, "X-Auth-Nonce": nonce, "X-Auth-Signature": sign}) ``` --- ## 九、测试 ```bash .mvn test # 单元 + 集成测试,共 40 个用例,不依赖外部环境 mvn package ``` - `SignUtilTest`:待签串构造、签名一致性、篡改检测 - `RouteMatchTest`:目标地址解析(占位符、剩余路径、query) - `GatewayIntegrationTest`:启动本地桩服务,端到端验证 JSON/XML/form/文本/二进制透传、 签名错误、nonce 重放、时间戳过期、缺头、路由不存在、审计日志落库、 **免鉴权路由无签名可访问 / 签名错误也放行 / 需鉴权路由仍拦截** - `AdminConsoleIntegrationTest`:未登录拦截重定向、错误口令拒绝、登录后页面与 API 可访问、 静态资源可访问、新增路由并绑定密钥的完整 CRUD、重复授权不撞唯一约束、编辑不重新生成 AK - `RouteTestServiceTest`:连通性测试服务,覆盖直连/经网关双模式可达、后端 5xx、后端不可达、 未匹配路由、需鉴权路由未选密钥提示、选密钥后签名通过、请求体与 query 透传 ### 冒烟测试(smoke,默认不执行) `BoeOemGatewayTest`、`ProdGatewaySmokeTest` 是**环境验收脚本**,依赖真实部署的网关与后端, 因此标记为 `@Tag("smoke")` 并在默认构建中排除(`pom.xml` 的 `test.skip.groups`), 避免 `mvn package` 因外部依赖不可用而失败。 | 类 | 依赖 | 默认目标 | |---|---|---| | `BoeOemGatewayTest` | 本地已启动的网关 | `http://127.0.0.1:8080` | | `ProdGatewaySmokeTest` | 远端已部署的网关 | `http://120.193.65.159:8888` | 需要验收时显式执行: ```bash # 本地冒烟:先启动网关 java -jar target/sn-agent.jar & mvn test -Dtest=BoeOemGatewayTest -Dtest.skip.groups= # 远端冒烟:地址与密钥可覆盖 mvn test -Dtest=ProdGatewaySmokeTest -Dtest.skip.groups= \ -Dgateway.url=http://网关IP:8888 -Dgateway.ak=xxx -Dgateway.sk=yyy ``` > 这两类断言的是「网关 + 路由 + 后端」整条链路: > `ConnectException` = 网关没启动;`502` = 网关正常但**后端不可达**,需排查后端服务本身。 --- ## 十、注意事项 1. **时钟同步**:客户端与网关需 NTP 同步,否则时间窗校验失败。 2. **nonce 唯一**:窗口内同一 AK 的 nonce 不可重复;当前实现为单实例 Caffeine, 多实例部署请将 `NonceStore` 替换为 Redis 实现(`SET key NX EX`)。 3. **SK 安全**:示例数据为明文存储,生产请改为加密存储或接入 KMS,并替换 `AppKeyService` 的取值逻辑。 4. **Body 限制**:默认 10MB 全量缓存(`security.max-body-size-bytes`)。若需转发大文件, 联系改造为流式转发模式。 5. **鉴权范围**:网关过滤器作用于 `/*`,通过 `proxy.exclude-paths` 排除管理台、自检与监控端点; `/actuator/**`、`/_agent/echo` 不参与 AK/SK 鉴权,生产请关闭 echo 并对 actuator 做访问控制。 6. **管理台安全**:登录后请立即修改默认密码;建议部署在内网并通过反向代理限制 `/admin` 来源 IP。 7. **默认账号**:仅当 `SNAG_ADMIN_USER` 表为空时初始化一次,删除该表记录后重启会重新生成默认账号。 6. **表单请求体**:已关闭 `FormContentFilter` 与 multipart 解析,保证网关能读到原始字节流。