Wren:IG 5.0.1 配置参数详解

详解 Wren:IG 5.0.1 的 config.json、heap 对象、Router 路由目录、condition 表达式、过滤器链、Groovy 脚本、JWT 会话、请求头注入、日志、JVM 堆、TLS 与管理模式,含逐项实测回显和生产配置模板。

Wren:IGWren:IG身份认证TomcatTLSWeb 服务器与反向代理2026/11/02

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重启 Tomcatjps -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配置根目录
heapconfig.json 顶层数组2 个对象声明可复用对象
heap[].nameconfig.json必填clientHandler、JwtSession对象引用名
heap[].typeconfig.json必填ClientHandler、org.forgerock.openig.jwt.JwtSessionManager对象类型
handlerconfig.json 顶层必填{"type":"Router"}顶层处理器
auditconfig.json 顶层审计服务名global指定审计服务
name路由文件必填app、health路由 ID
condition路由文件可选${request.uri.path == '/app'}匹配条件
baseURI路由文件可选http://127.0.0.1:8081上游基地址
session路由文件可选JwtSession绑定会话管理器
handler路由文件必填Chain / clientHandler路由处理器
sessionTimeoutJwtSessionManager30 minutes30 minutes会话有效期
cookieNameJwtSessionManageropenig-jwt-sessionwrenig-session会话 Cookie 名
messageTypeHeaderFilter必填REQUEST改写方向
addHeaderFilter可选两个头增加请求头
removeHeaderFilter可选Cookie删除请求头
typeScriptableFilter必填application/x-groovy脚本类型
fileScriptableFilter必填/opt/wrenig/scripts/auth.groovy脚本路径
status / reason / headers / entityStaticResponseHandler按需200 / OK / JSON / 文本静态响应
scanIntervalRouter(内部默认)约 10 秒未显式改路由目录扫描周期
root levellogback.xmlINFODEBUG(实测)日志级别
-Xms / -Xmxsetenv.sh由 JVM 决定256m / 512m堆上下限
modeadmin.jsonEVALUATION(缺失文件时)先 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"
    }
}

参数说明:

参数默认值本文值说明
sessionTimeout30 minutes30 minutes会话有效期
cookieNameopenig-jwt-sessionwrenig-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 脚本里,做三件事:

  1. 读取 Authorization 头;
  2. 与固定 Basic 凭据比对,不匹配返回 401 并带 WWW-Authenticate;
  3. 匹配则写会话并注入 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\" }"
        }
    }
}
参数说明
statusHTTP 状态码,本文 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>

参数说明:

参数本文值说明
port8443HTTPS 监听端口
protocolHttp11NioProtocolNIO 连接器
SSLEnabledtrue启用 TLS
certificateKeystoreFile/opt/wrenig/keystore.jks密钥库路径
certificateKeystorePasswordchangeit密钥库口令
typeRSA证书算法类型

实测 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。

排查顺序:

  1. 确认文件在 /opt/wrenig/config/routes/,不是 /opt/tomcat/conf/routes/
  2. 确认 JSON 语法正确:python3 -m json.tool <文件>
  3. 确认文件对 wrenig 用户可读
  4. 等待约 10 秒扫描周期
  5. 查看 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 5.0.1 配置参数详解 - Quzuo