Wren:IG 5.0.1 配置参数详解
详解 Wren:IG 5.0.1 的 config.json、heap 对象、Router 路由目录、condition 表达式、过滤器链、Groovy 脚本、JWT 会话、请求头注入、日志、JVM 堆、TLS 与管理模式,含逐项实测回显和生产配置模板。
Wren:IG 5.0.1 配置参数详解
目标:把 Wren:IG 5.0.1 的配置模型拆成可操作的参数清单,覆盖
config.json、heap 对象、Router 路由目录、condition 表达式、过滤器链、Groovy 脚本、JWT 会话、请求头注入、日志、JVM 堆、TLS 与管理模式,并给出逐项实测回显与一套可直接套用的生产模板。本文已在 Ubuntu 22.04.5 LTS + Tomcat 9.0.123 + Wren:IG 5.0.1 环境实测通过。
一、环境信息
本节给出配置对象所处的运行基线,避免读者在别的版本上照抄参数。
- 操作系统:Ubuntu 22.04.5 LTS,x86_64
- JDK:OpenJDK 17.0.20.1
- Servlet 引擎:Apache Tomcat 9.0.123
- Wren:IG:5.0.1,修订号
c9eaf68a44e0e03c374bd345ac99731c38b25528 - 配置根目录:
/opt/wrenig(由OPENIG_BASE指定) - 主配置:
/opt/wrenig/config/config.json - 路由目录:
/opt/wrenig/config/routes/ - 脚本目录:
/opt/wrenig/scripts/ - 日志目录:
/opt/wrenig/logs/ - HTTP/HTTPS 端口:8080 / 8443
- 验证用上游服务:
127.0.0.1:8081
二、前置条件
本节确认服务已经可按最小配置启动,否则后续参数修改没有验证对象。
- 已按安装指南完成 JDK、Tomcat 与 WAR 部署
/opt/wrenig/config/config.json与至少一条路由已存在wrenig用户对/opt/wrenig与/opt/tomcat有读写权限- 本机可访问
http://127.0.0.1:8080/wrenig/api/info - 如需验证上游转发,另有一个监听
127.0.0.1:8081的 HTTP 服务
先用管理信息端点确认服务在线:
curl -sS http://127.0.0.1:8080/wrenig/api/info
实测回显:
{"version":"5.0.1","revision":"c9eaf68a44e0e03c374bd345ac99731c38b25528","branch":"UNKNOWN","timestamp":1770208153544}
返回 version 与 revision 即说明配置已被加载;如果这里失败,先回到安装阶段排查,不要继续改参数。
三、参数查看与修改方式
本节说明“改哪个文件、改完要不要重启、怎么确认生效”,这是后续所有参数章节的通用方法。
3.1 查看主配置
cat /opt/wrenig/config/config.json
实测回显:
{
"heap": [
{
"name": "clientHandler",
"type": "ClientHandler"
},
{
"name": "JwtSession",
"type": "org.forgerock.openig.jwt.JwtSessionManager",
"config": {
"sessionTimeout": "30 minutes",
"cookieName": "wrenig-session"
}
}
],
"handler": {
"type": "Router"
},
"audit": "global"
}
3.2 查看路由配置
cat /opt/wrenig/config/routes/app.json
实测回显:
{
"name": "app",
"condition": "${request.uri.path == '/app'}",
"baseURI": "http://127.0.0.1:8081",
"session": "JwtSession",
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "ScriptableFilter",
"config": {
"type": "application/x-groovy",
"file": "/opt/wrenig/scripts/auth.groovy"
}
},
{
"type": "HeaderFilter",
"config": {
"messageType": "REQUEST",
"add": {
"X-Authenticated-User": [ "admin" ],
"X-Gateway": [ "WrenIG" ]
},
"remove": [ "Cookie" ]
}
}
],
"handler": "clientHandler"
}
}
}
3.3 修改后是否需要重启
| 变更对象 | 生效方式 | 实测依据 |
|---|---|---|
config.json | 重启 Tomcat | 启动日志会重新读取 /opt/wrenig/config/config.json |
admin.json | 重启 Tomcat | 启动日志显示读取 admin.json |
config/routes/*.json | 热加载,默认约 10 秒 | 新增路由后不重启即返回 200,日志出现非 main 线程加载记录 |
scripts/*.groovy | 下一次请求重新加载脚本 | 脚本修改后无需重启,请求即按新脚本执行 |
config/logback.xml | 重启 Tomcat | 重启后 catalina.out 出现 DEBUG 行 |
bin/setenv.sh | 重启 Tomcat | jps -v 显示新 JVM 参数 |
修改 JSON 后先做语法校验,避免把坏配置带进重启:
python3 -m json.tool /opt/wrenig/config/config.json > /dev/null && echo "JSON OK"
实测回显:
JSON OK
3.4 用管理 API 核对已加载的路由
curl -sS 'http://127.0.0.1:8080/wrenig/api/system/objects/router-handler/routes?_queryFilter=true'
实测输出节选(app 路由部分):
{"result":[{"_id":"app","name":"app","condition":"${request.uri.path == '/app'}","baseURI":"http://127.0.0.1:8081","session":"JwtSession","handler":{"type":"Chain","config":{"filters":[{"type":"ScriptableFilter","config":{"type":"application/x-groovy","file":"/opt/wrenig/scripts/auth.groovy"}},{"type":"HeaderFilter","config":{"messageType":"REQUEST","add":{"X-Authenticated-User":["admin"],"X-Gateway":["WrenIG"]}},"remove":["Cookie"]}}],"handler":"clientHandler"}}}],"resultCount":2 ...
解读:API 返回的内容应与磁盘上的路由文件一致。若 API 里没有刚写入的路由,说明 Router 尚未扫描到,等待 10 秒后重试;若内容不一致,说明改错了目录。
四、参数总览
本节把散落在多个文件中的参数集中成一张表,便于快速定位。
| 参数/键 | 所在位置 | 默认值或约束 | 本文实测值 | 作用 |
|---|---|---|---|---|
OPENIG_BASE | 环境变量 / 系统属性 / Servlet 初始化参数 | 无默认,必须指定 | /opt/wrenig | 配置根目录 |
heap | config.json 顶层 | 数组 | 2 个对象 | 声明可复用对象 |
heap[].name | config.json | 必填 | clientHandler、JwtSession | 对象引用名 |
heap[].type | config.json | 必填 | ClientHandler、org.forgerock.openig.jwt.JwtSessionManager | 对象类型 |
handler | config.json 顶层 | 必填 | {"type":"Router"} | 顶层处理器 |
audit | config.json 顶层 | 审计服务名 | global | 指定审计服务 |
name | 路由文件 | 必填 | app、health | 路由 ID |
condition | 路由文件 | 可选 | ${request.uri.path == '/app'} | 匹配条件 |
baseURI | 路由文件 | 可选 | http://127.0.0.1:8081 | 上游基地址 |
session | 路由文件 | 可选 | JwtSession | 绑定会话管理器 |
handler | 路由文件 | 必填 | Chain / clientHandler | 路由处理器 |
sessionTimeout | JwtSessionManager | 30 minutes | 30 minutes | 会话有效期 |
cookieName | JwtSessionManager | openig-jwt-session | wrenig-session | 会话 Cookie 名 |
messageType | HeaderFilter | 必填 | REQUEST | 改写方向 |
add | HeaderFilter | 可选 | 两个头 | 增加请求头 |
remove | HeaderFilter | 可选 | Cookie | 删除请求头 |
type | ScriptableFilter | 必填 | application/x-groovy | 脚本类型 |
file | ScriptableFilter | 必填 | /opt/wrenig/scripts/auth.groovy | 脚本路径 |
status / reason / headers / entity | StaticResponseHandler | 按需 | 200 / OK / JSON / 文本 | 静态响应 |
scanInterval | Router(内部默认) | 约 10 秒 | 未显式改 | 路由目录扫描周期 |
| root level | logback.xml | INFO | DEBUG(实测) | 日志级别 |
-Xms / -Xmx | setenv.sh | 由 JVM 决定 | 256m / 512m | 堆上下限 |
mode | admin.json | EVALUATION(缺失文件时) | 先 EVALUATION 后 PRODUCTION | 管理模式 |
如何使用这张表:先按“想改什么”找到参数,再看“所在位置”和“生效方式”(见第三章 3.3)。不要在没有备份的情况下同时改多个文件,否则出错时无法判断是哪一项导致。
五、核心参数分类详解
本节按配置模型分层展开,每小节都给出参数含义、实测配置、实测回显与排障要点。
5.1 配置根目录 OPENIG_BASE
OPENIG_BASE 是 Wren:IG 定位所有配置、脚本、日志、数据的根。它有三个传入位置,优先级与适用场景如下:
| 传入方式 | 写法 | 适用场景 |
|---|---|---|
| 环境变量 | export OPENIG_BASE=/opt/wrenig | 脚本启动、systemd 托管 |
| 系统属性 | -Dopenig.base=/opt/wrenig | 需要覆盖环境变量的启动方式 |
| Servlet 初始化参数 | openig-base | 多应用共用一个引擎时的隔离 |
本文采用环境变量,写在 /opt/tomcat/bin/setenv.sh:
cat /opt/tomcat/bin/setenv.sh
实测回显:
export OPENIG_BASE=/opt/wrenig
export CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
启动日志会回显实际读取到的值:
[main] INFO o.f.openig.web.OpenIGInitializer - OpenIG base directory : /opt/wrenig
[main] INFO o.f.openig.web.OpenIGInitializer - Reading the configuration from /opt/wrenig/config/config.json
⚠️ 注意:
OPENIG_BASE指向的目录必须对运行用户可读可写。日志写入/opt/wrenig/logs,配置热加载会读取config/routes,权限不足时会出现“配置未加载但进程还活着”的假象。
5.2 config.json 顶层结构
config.json 是一个 JSON 对象,顶层常用三个键:
{
"heap": [],
"handler": {},
"audit": "global"
}
| 键 | 是否必填 | 说明 |
|---|---|---|
heap | 是 | 对象堆,声明可复用的处理器、会话管理器、静态响应器等 |
handler | 是 | 顶层处理器;本文固定为 {"type":"Router"} |
audit | 否 | 审计服务引用名;本文保留 global |
关键点:heap 里的对象通过 name 被其它地方引用。顶层的 handler 可以直接写对象定义,也可以写 heap 中的对象名。本文采用“heap 定义 + 顶层直接写 Router”的混合写法。
💡 技术拓展:Wren:IG 的 handler 组织方式不止一种——
方式 写法 适用场景 单 handler 顶层 handler直接是一个 Handler 对象只有一条固定链路 路由分派 顶层 handler为 Router,按目录加载路由多应用、多条件 链式组合 路由内 handler为 Chain,串联多个 Filter需要认证、改写、会话 本文采用“路由分派 + 链式组合”:顶层 Router 负责选路,路由内 Chain 负责处理。
5.3 heap 对象:ClientHandler
ClientHandler 是真正把请求发往上游的处理器。本文定义如下:
{
"name": "clientHandler",
"type": "ClientHandler"
}
它本身不需要额外参数;超时、连接池等参数属于更高级的客户端配置,本文未修改。路由通过字符串 "handler": "clientHandler" 引用它。
验证它确实在工作:请求 /app 时,上游回显了完整请求信息:
{"method": "GET", "path": "/app", "headers": {"accept": "*/*", "authorization": "Basic YWRtaW46cTF3MmUzcjQ=", "host": "127.0.0.1:8080", "user-agent": "curl/7.81.0", "X-Authenticated-User": "admin", "X-Gateway": "WrenIG", "Connection": "Keep-Alive"}}
解读:host 是上游看到的 Host 头;X-Authenticated-User 与 X-Gateway 是过滤器注入的;请求里没有 cookie 键,说明 HeaderFilter 的 remove 已生效。
5.4 Router 与路由目录
Router 是 Wren:IG 的“自动发现式分派器”。它不读取 config.json 里的内联路由数组,而是扫描 /opt/wrenig/config/routes/ 目录,把每个 JSON 文件当作一条路由。
"handler": {
"type": "Router"
}
路由文件命名没有强制约束,但建议用路由 ID 命名,便于排障:
/opt/wrenig/config/routes/app.json
/opt/wrenig/config/routes/health.json
5.4.1 热加载实测
新增一条 health.json 后不重启,等待默认扫描周期,请求立即生效:
curl -sS -i http://127.0.0.1:8080/health
实测回显:
HTTP/1.1 200
Content-Type: application/json
Content-Length: 40
{ "status": "UP", "product": "Wren:IG" }
日志出现非 main 线程的加载记录:
[pool-1-thread-1] INFO o.f.o.handler.router.RouterHandler - Loaded the route with id 'health' registered with the name 'health'
解读:线程名从 main 变为 pool-1-thread-1,说明这是扫描线程在运行期加载,而不是启动期一次性加载。本文实测从写入文件到请求返回 200 约 13 秒,与默认约 10 秒的扫描间隔相符。
5.4.2 常见错误
把路由内联到 config.json:
"handler": {
"type": "Router",
"config": { "routes": [ { "name": "app" } ] }
}
这种写法不会生效,日志会出现:
[http-nio-8080-exec-5] ERROR o.f.o.handler.router.RouterHandler - no handler to dispatch to
请求返回 404。正确做法是把每条路由拆成独立文件放进 config/routes/。
5.5 路由文件字段
一条路由由以下字段组成:
| 字段 | 必填 | 示例 | 说明 |
|---|---|---|---|
name | 是 | app | 路由 ID,同时作为日志文件名 route-app.log |
condition | 否 | ${request.uri.path == '/app'} | 匹配条件;省略则匹配所有请求 |
baseURI | 否 | http://127.0.0.1:8081 | 上游基地址 |
session | 否 | JwtSession | 引用 heap 中的会话管理器 |
handler | 是 | Chain / clientHandler | 路由处理器 |
字段解读:baseURI 只提供协议、主机与端口,实际转发路径由请求 URI 决定。例如请求 /app 会转发到 http://127.0.0.1:8081/app;上游回显的 path 正是 /app。
5.6 condition 表达式
condition 使用 EL(Expression Language)表达式,返回布尔值。常见写法:
| 目的 | 表达式 |
|---|---|
| 精确匹配路径 | ${request.uri.path == '/app'} |
| 前缀匹配 | ${matches(request.uri.path, '^/api/')} |
| 匹配方法 | ${request.method == 'POST'} |
| 匹配主机 | ${request.uri.host == 'app.example.com'} |
本文使用精确匹配:
"condition": "${request.uri.path == '/app'}"
实测:/app 返回 200(带正确凭据),/not-exist 返回 404,说明条件只命中了 /app。
⚠️ 注意:
condition是配置语法的一部分,不是 shell 变量。不要用 shell 的$展开规则去理解它;在 JSON 中它就是一个普通字符串。
5.7 Handler 与 Chain
路由的 handler 可以是单个 handler,也可以是 Chain。Chain 把多个过滤器串在一条 handler 前面:
{
"type": "Chain",
"config": {
"filters": [
{
"type": "HeaderFilter",
"config": {
"messageType": "REQUEST",
"add": { "X-Gateway": [ "WrenIG" ] }
}
}
],
"handler": "clientHandler"
}
}
执行顺序为:请求按 filters 数组顺序依次经过,最后交给 handler;响应按相反顺序返回。本文的链路是:
请求 → ScriptableFilter(认证/会话) → HeaderFilter(注入头、删 Cookie) → ClientHandler → 上游
为什么这样排:认证必须在注入身份头之前,否则未认证请求也会被加上 X-Authenticated-User。HeaderFilter 放在认证之后,才能保证注入的头只出现在已认证请求里。
5.8 HeaderFilter
HeaderFilter 用于增删请求头或响应头。本文配置:
{
"type": "HeaderFilter",
"config": {
"messageType": "REQUEST",
"add": {
"X-Authenticated-User": [ "admin" ],
"X-Gateway": [ "WrenIG" ]
},
"remove": [ "Cookie" ]
}
}
参数逐项说明:
| 参数 | 必填 | 本文值 | 说明 |
|---|---|---|---|
messageType | 是 | REQUEST | 改写请求头;也可设为 RESPONSE 改写响应头 |
add | 否 | 两个头 | 增加值;同名头可给数组 |
remove | 否 | Cookie | 删除指定头,避免会话 Cookie 泄露给上游 |
⚠️ 注意:增加头使用
add,不是set。set不是合法键,配置后会报错。缺少messageType时会报:/handler/config/filters/0/config/messageType: Expecting a value
实测效果:上游回显中出现 X-Authenticated-User: admin 与 X-Gateway: WrenIG,且没有 cookie 键,证明增删都生效。
5.9 ScriptableFilter 与 Groovy 脚本
ScriptableFilter 加载 Groovy 脚本,把脚本的执行结果作为过滤器结果。本文配置:
{
"type": "ScriptableFilter",
"config": {
"type": "application/x-groovy",
"file": "/opt/wrenig/scripts/auth.groovy"
}
}
| 参数 | 必填 | 本文值 | 说明 |
|---|---|---|---|
type | 是 | application/x-groovy | 脚本 MIME 类型 |
file | 是 | /opt/wrenig/scripts/auth.groovy | 脚本绝对路径,运行用户必须可读 |
脚本内容:
import org.forgerock.http.protocol.Response
import org.forgerock.http.protocol.Status
import org.forgerock.http.session.SessionContext
def auth = request.headers.getFirst("Authorization")
if (auth != "Basic YWRtaW46cTF3MmUzcjQ=") {
def denied = new Response(Status.UNAUTHORIZED)
denied.headers.add("WWW-Authenticate", 'Basic realm="WrenIG"')
return denied
}
context.asContext(SessionContext).getSession().put("authenticated-user", "admin")
request.headers.add("X-Authenticated-User", "admin")
return next.handle(context, request)
脚本绑定变量:
| 变量 | 类型/用途 |
|---|---|
request | 当前请求,可读 URI、头、实体 |
context | 上下文链,可取出会话、事务 ID 等 |
next | 下一处理器;调用 next.handle(context, request) 才继续链路 |
实测效果:
- 不带凭据:返回 401,响应头包含
WWW-Authenticate: Basic realm="WrenIG" - 错误口令:返回 401
- 正确口令:返回 200,上游收到
X-Authenticated-User: admin,并返回Set-Cookie会话 Cookie(值见 5.10.1 节实测)
⚠️ 注意:取会话不能用
context.getContext(SessionContext)。本文实测该写法报:groovy.lang.MissingMethodException: No signature of method: org.forgerock.services.context.TransactionIdContext.getContext() is applicable for argument types: (Class) values: [class org.forgerock.http.session.SessionContext]正确写法是
context.asContext(SessionContext).getSession()。
5.10 会话与 JwtSessionManager
JwtSessionManager 把会话数据加密后写入 Cookie。本文配置:
{
"name": "JwtSession",
"type": "org.forgerock.openig.jwt.JwtSessionManager",
"config": {
"sessionTimeout": "30 minutes",
"cookieName": "wrenig-session"
}
}
参数说明:
| 参数 | 默认值 | 本文值 | 说明 |
|---|---|---|---|
sessionTimeout | 30 minutes | 30 minutes | 会话有效期 |
cookieName | openig-jwt-session | wrenig-session | 会话 Cookie 名 |
cookieDomain | 未设置 | 未设置 | 跨域共享会话时设置 |
cookiePath | / | / | Cookie 作用路径 |
keystore / alias / password | 无 | 未设置 | 加密密钥材料;生产多实例必须配置 |
⚠️ 注意:
type必须写全限定类名org.forgerock.openig.jwt.JwtSessionManager。写短名JwtSessionManager会报类找不到。
5.10.1 会话有效期实测
正确凭据请求返回的 Set-Cookie 中,Expires 比响应时间晚 30 分钟:
Set-Cookie: wrenig-session=eyJ0eXAiOiJKV1QiLCJjdHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...; Expires=Mon, 02 Nov 2026 13:21:32 GMT; Path=/
(输出节选,实测首尾:Cookie 值中间为 JWT 串,此处省略。)
响应时间为 12:51:32 GMT,Expires 为 13:21:32 GMT,正好 30 分钟,证明 sessionTimeout 生效。
5.10.2 未配置密钥时的告警
启动日志会出现两条 WARN(输出节选,实测首尾,末尾省略号为原日志截断):
[main] WARN o.f.o.jwt.JwtSessionManager$Heaplet - JWT session support has been enabled but no encryption keys have been configured. A temporary key pair will be used ...
[main] WARN o.f.o.jwt.JwtSessionManager$Heaplet - No shared secret have been configured for JWT session authenticity verification. A temporary key will be used ...
影响:单实例测试无碍;服务重启后旧 Cookie 无法解密,多实例之间也无法互认。生产环境必须配置固定密钥,并让同一集群的所有实例共享同一套密钥材料。
5.11 认证与身份头注入
本文的入站认证逻辑放在 Groovy 脚本里,做三件事:
- 读取
Authorization头; - 与固定 Basic 凭据比对,不匹配返回 401 并带
WWW-Authenticate; - 匹配则写会话并注入
X-Authenticated-User,再调用next.handle。
实测三种请求:
curl -sS -i http://127.0.0.1:8080/app
HTTP/1.1 401
WWW-Authenticate: Basic realm="WrenIG"
Transfer-Encoding: chunked
curl -sS -i -u admin:wrong http://127.0.0.1:8080/app
HTTP/1.1 401
WWW-Authenticate: Basic realm="WrenIG"
Transfer-Encoding: chunked
curl -sS -i -u admin:q1w2e3r4 http://127.0.0.1:8080/app
HTTP/1.1 200
Set-Cookie: wrenig-session=...; Expires=Mon, 02 Nov 2026 13:21:32 GMT; Path=/
Content-Type: application/json
{"method": "GET", "path": "/app", "headers": {"accept": "*/*", "authorization": "Basic YWRtaW46cTF3MmUzcjQ=", "host": "127.0.0.1:8080", "user-agent": "curl/7.81.0", "X-Authenticated-User": "admin", "X-Gateway": "WrenIG", "Connection": "Keep-Alive"}}
(上文为实测输出节选,Set-Cookie 中的 JWT 串已用省略号代替。)
解读:401 响应没有正文,只带挑战头,浏览器会弹出登录框;200 响应里上游能看到注入的身份头。生产环境不应把明文口令硬编码在脚本里,应改为校验上游 IdP 的令牌或调用认证服务。
💡 技术拓展:网关的认证实现方式有多种——
方式 特点 适用场景 Basic 凭据校验 实现简单,凭据随每次请求发送 内网测试、快速验证 令牌校验(JWT/OIDC) 校验签名与过期时间,无状态 现代 SSO、微服务 会话 Cookie 登录一次,后续靠会话 传统 Web 应用 委托外部认证服务 网关只做转发与头注入 已有 IdP 的企业 本文为演示链路采用 Basic 凭据校验;生产应优先使用令牌或外部 IdP 校验。
5.12 StaticResponseHandler
StaticResponseHandler 不访问上游,直接返回固定响应,适合健康检查、维护页、最小验证。
{
"name": "health",
"condition": "${request.uri.path == '/health'}",
"handler": {
"type": "StaticResponseHandler",
"config": {
"status": 200,
"reason": "OK",
"headers": {
"Content-Type": [ "application/json" ]
},
"entity": "{ \"status\": \"UP\", \"product\": \"Wren:IG\" }"
}
}
}
| 参数 | 说明 |
|---|---|
status | HTTP 状态码,本文 200 |
reason | 原因短语,本文 OK |
headers | 响应头,值为数组 |
entity | 响应正文;JSON 内层引号需转义 |
实测返回:
HTTP/1.1 200
Content-Type: application/json
Content-Length: 40
{ "status": "UP", "product": "Wren:IG" }
5.13 审计配置
config.json 顶层的 audit 指定审计服务。本文沿用 OpenIG 配置形态:
"audit": "global"
说明:audit 的值应与 heap 中定义的审计服务对象对应;本文最小配置没有展开具体审计输出,启动日志未出现审计相关错误。需要把访问记录落盘或发往 Syslog 时,应在 heap 中定义审计服务并把 audit 指向它的 name,再验证日志文件生成。
⚠️ 注意:不要因为“启动没报错”就认为审计已落盘。审计是否生效要看目标文件/接收端是否真的出现记录;本文未把审计落盘纳入实测范围。
5.14 日志与 logback
Wren:IG 使用 logback。默认配置把日志同时输出到控制台与按路由拆分的文件:
| 输出 | 位置 | 滚动策略 |
|---|---|---|
| 控制台 | catalina.out | 由 Tomcat 管理 |
| 路由日志 | /opt/wrenig/logs/route-<routeId>.log | 单文件 100MB、保留 30 天、总量 3GB |
| 系统日志 | /opt/wrenig/logs/route-system.log | 同上,routeId 默认为 system |
外部覆盖文件为 /opt/wrenig/config/logback.xml。本文把 root 级别从 INFO 改为 DEBUG 后重启,catalina.out 出现大量 DEBUG 行,实测计数 998 行,例如:
[I/O dispatcher 1] DEBUG o.a.h.i.n.c.InternalHttpAsyncClient - [exchange: 1] Response processed
日志文件中的格式为:
12:44:53:711 | DEBUG | http-nio-8080-exec-1 | o.a.h.i.n.c.MainClientExec | [exchange: 1] start execution
调优建议:生产默认保持 INFO;排障时临时改 DEBUG,复现后立即改回。DEBUG 会把上游连接的每次读写都打出来,日志量增长很快。
5.15 JVM 堆参数
JVM 参数位于 /opt/tomcat/bin/setenv.sh,通过 CATALINA_OPTS 传入:
export CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
验证实际生效值:
jps -v
实测输出节选(Bootstrap 行):
7315 Bootstrap ... -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp
再用 ps 核对进程用户与参数:
ps -eo pid,user,args | grep org.apache.catalina.startup.Bootstrap | grep -v grep
实测输出节选:
7315 wrenig /usr/bin/java ... -Xms256m -Xmx512m -Dfile.encoding=UTF-8 ... org.apache.catalina.startup.Bootstrap start
解读:-Xms256m -Xmx512m 出现在进程命令行,说明堆限制已生效;user 为 wrenig,说明没有以 root 运行。
⚠️ 注意:只改
setenv.sh不会影响已经运行的进程,必须重启 Tomcat 后再用jps -v复核。若发现-Xmx被更高优先级的启动脚本覆盖,检查catalina.sh、startup.sh与 systemd 单元中的重复定义。
5.16 TLS 配置
TLS 由 Tomcat 的 Connector 提供,Wren:IG 本身不管理证书。本文在 /opt/tomcat/conf/server.xml 的 </Service> 前增加:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/opt/wrenig/keystore.jks"
certificateKeystorePassword="changeit"
type="RSA" />
</SSLHostConfig>
</Connector>
参数说明:
| 参数 | 本文值 | 说明 |
|---|---|---|
port | 8443 | HTTPS 监听端口 |
protocol | Http11NioProtocol | NIO 连接器 |
SSLEnabled | true | 启用 TLS |
certificateKeystoreFile | /opt/wrenig/keystore.jks | 密钥库路径 |
certificateKeystorePassword | changeit | 密钥库口令 |
type | RSA | 证书算法类型 |
实测 HTTPS 访问:
curl -skS -i https://127.0.0.1:8443/health
HTTP/1.1 200
Content-Type: application/json
Content-Length: 40
{ "status": "UP", "product": "Wren:IG" }
查看证书主题:
echo -n "" | openssl s_client -connect 127.0.0.1:8443 2>/dev/null | openssl x509 -noout -subject -dates
subject=C = CN, L = Shanghai, O = Wren Security, CN = wrenig.example.com
notBefore=Nov 2 12:38:36 2026 GMT
notAfter=Nov 2 12:38:36 2027 GMT
⚠️ 注意:
curl加-k是因为本文使用自签名证书。生产环境应换成企业 CA 证书,并去掉-k验证链。
5.17 admin.json 与管理模式
/opt/wrenig/config/admin.json 控制管理端点的暴露方式。文件缺失时按 EVALUATION 模式运行,启动日志会警告:
[main] WARN o.f.openig.web.OpenIGInitializer - The product is running in EVALUATION mode - by default, all endpoints are open and accessible. Do not use this mode for a production environment.
[main] INFO o.f.openig.web.OpenIGInitializer - /opt/wrenig/config/admin.json not readable, using OpenIG's default-admin.json
写入 PRODUCTION 模式:
{
"mode": "PRODUCTION"
}
重启后实测:
curl -sS -i http://127.0.0.1:8080/wrenig/api/info
HTTP/1.1 404
Transfer-Encoding: chunked
即使从本机回环地址访问,管理 API 也返回 404;删除 admin.json 并重启后恢复 200。解读:PRODUCTION 模式默认收紧了管理端点,不能把它当作“只是关闭一个警告”;生产使用管理 API 必须设计受控通道。
六、生产配置模板
本节给出一套可直接落地的配置组合,适用于“单实例、入口做 Basic/令牌校验、向上游注入身份头”的场景。模板中的路径、端口与密钥库均按本文实测环境填写。
6.1 config.json
{
"heap": [
{
"name": "clientHandler",
"type": "ClientHandler"
},
{
"name": "JwtSession",
"type": "org.forgerock.openig.jwt.JwtSessionManager",
"config": {
"sessionTimeout": "30 minutes",
"cookieName": "wrenig-session"
}
}
],
"handler": {
"type": "Router"
},
"audit": "global"
}
6.2 路由 app.json
{
"name": "app",
"condition": "${request.uri.path == '/app'}",
"baseURI": "http://127.0.0.1:8081",
"session": "JwtSession",
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "ScriptableFilter",
"config": {
"type": "application/x-groovy",
"file": "/opt/wrenig/scripts/auth.groovy"
}
},
{
"type": "HeaderFilter",
"config": {
"messageType": "REQUEST",
"add": {
"X-Authenticated-User": [ "admin" ],
"X-Gateway": [ "WrenIG" ]
},
"remove": [ "Cookie" ]
}
}
],
"handler": "clientHandler"
}
}
}
6.3 路由 health.json
{
"name": "health",
"condition": "${request.uri.path == '/health'}",
"handler": {
"type": "StaticResponseHandler",
"config": {
"status": 200,
"reason": "OK",
"headers": {
"Content-Type": [ "application/json" ]
},
"entity": "{ \"status\": \"UP\", \"product\": \"Wren:IG\" }"
}
}
}
6.4 认证脚本 auth.groovy
import org.forgerock.http.protocol.Response
import org.forgerock.http.protocol.Status
import org.forgerock.http.session.SessionContext
def auth = request.headers.getFirst("Authorization")
if (auth != "Basic YWRtaW46cTF3MmUzcjQ=") {
def denied = new Response(Status.UNAUTHORIZED)
denied.headers.add("WWW-Authenticate", 'Basic realm="WrenIG"')
return denied
}
context.asContext(SessionContext).getSession().put("authenticated-user", "admin")
request.headers.add("X-Authenticated-User", "admin")
return next.handle(context, request)
6.5 JVM 与日志
/opt/tomcat/bin/setenv.sh:
export OPENIG_BASE=/opt/wrenig
export CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
/opt/wrenig/config/logback.xml 的 root 级别建议:
<root level="INFO">
<appender-ref ref="SIFT" />
<appender-ref ref="STDOUT" />
</root>
6.6 模板适用条件
| 条件 | 说明 |
|---|---|
| 内存 | 2–4GB,堆 256–512MB |
| 并发 | 小到中等;高并发需评估 ClientHandler 连接池与堆 |
| 部署 | 单实例;多实例需配置共享 JWT 密钥或集中会话 |
| 认证 | 演示级 Basic 校验;生产应替换为令牌/IdP 校验 |
| 审计 | 未展开落盘;合规场景需补充审计服务定义 |
| 证书 | 自签名仅用于测试;生产替换为企业 CA |
七、常见问题
7.1 改完 config.json 后不生效
现象:修改 config.json 并保存,请求行为不变。
原因:config.json 只在启动时读取,不支持热加载。
解决:重启 Tomcat;重启后用 /wrenig/api/info 确认服务恢复,再验证业务路由。
7.2 新增路由文件后仍 404
现象:路由文件已放入 config/routes/,请求仍返回 404。
排查顺序:
- 确认文件在
/opt/wrenig/config/routes/,不是/opt/tomcat/conf/routes/ - 确认 JSON 语法正确:
python3 -m json.tool <文件> - 确认文件对
wrenig用户可读 - 等待约 10 秒扫描周期
- 查看
catalina.out是否出现Loaded the route with id关键字
7.3 HeaderFilter 报错 Expecting a value
现象:请求 500,日志出现:
/handler/config/filters/0/config/messageType: Expecting a value
原因:缺少 messageType。
解决:补 "messageType": "REQUEST" 或 "RESPONSE"。同时确认增加值使用 add,不是 set。
7.4 Groovy 取会话报 MissingMethodException
现象:
groovy.lang.MissingMethodException: No signature of method: org.forgerock.services.context.TransactionIdContext.getContext() is applicable for argument types: (Class) values: [class org.forgerock.http.session.SessionContext]
原因:使用了不存在的 getContext(Class)。
解决:改为 context.asContext(SessionContext).getSession()。
7.5 JWT 会话重启后失效
现象:服务重启后,浏览器带着旧 Cookie 访问,会话读不到或报 JWT 解析错误。
原因:未配置固定密钥,Wren:IG 使用临时密钥,重启后无法解密旧 Cookie。
解决:在 JwtSessionManager 配置中提供固定 keystore/alias/password 与共享签名密钥,并在所有实例间保持一致。
7.6 PRODUCTION 模式下管理 API 全 404
现象:写入 admin.json 的 {"mode":"PRODUCTION"} 并重启后,/wrenig/api/info 返回 404,回环访问也一样。
原因:生产模式默认收紧管理端点。
解决:确认是否真的需要在线管理 API;需要时设计受控的内网通道或临时切回 EVALUATION 进行维护,维护完成后再切回 PRODUCTION。
7.7 日志 DEBUG 打开后磁盘快速增长
现象:把 root 级别改为 DEBUG 后,catalina.out 与 route-*.log 快速变大。
原因:DEBUG 会记录上游连接的每次读写。
解决:排障完成后立即改回 INFO 并重启;必要时清理历史日志,保留滚动策略中的 30 天/3GB 上限。
八、参考链接
- Wren:IG 官方项目页:https://wrensecurity.org/projects/wrenig/
- Wren:IG 官方文档站:https://docs.wrensecurity.org/wrenig/latest/
- Wren:IG 5.0.1 发布页:https://github.com/WrenSecurity/wrenig/releases/tag/5.0.1
- Apache Tomcat 9 配置参考:https://tomcat.apache.org/tomcat-9.0-doc/config/
- Apache Tomcat 9 SSL 配置:https://tomcat.apache.org/tomcat-9.0-doc/config/http.html
- Logback 配置参考:https://logback.qos.ch/manual/configuration.html