如何为Log4j2 HTTP Appender生成可自动刷新的授权头?
技术问询
是否有合适的方法为Log4j2 HTTP Appender创建每次POST请求时可重新生成的Authorization头?
背景说明
每次向API提交POST数据时都必须计算Authorization头,这是该API唯一接受的认证方式,由签名、ID和时间戳组成。由于API不接受超过X分钟的旧认证头,因此无法将其固定设置为环境变量。
当前实现及问题
我尝试在Log4j2配置中使用脚本生成该认证头并用于HTTP Appender,参考了Log4j2的脚本配置文档。
Log4j2配置文件(log4j2http.xml)
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="debug" name="MyApp"> <Scripts> <Script name="selector" language="groovy"> <![CDATA[ import org.apache.commons.codec.binary.Hex import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import java.security.MessageDigest def id="XXXXXXXXXXXXXx" Long epoch_time = System.currentTimeMillis() Mac hmac = Mac.getInstance("HmacSHA256") hmac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")) def signature = Hex.encodeHexString(hmac.doFinal("GET${epoch_time}${path}".getBytes())).bytes.encodeBase64() return "${id}:${signature}:${epoch_time}" ]]> </Script> </Scripts> <Appenders> <Http name="Http" url="https://XXX/rest/log/ingest" method="POST"> <KeyValuePair key="_Id:" value="{Id:"588"}" /> <Property name="Authorization" value="${result}" /> <Property name="Accept" value="application/json" /> <JsonLayout properties="true" /> </Http> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="Http" /> </Root> </Loggers> </Configuration>
应用代码
package App; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import java.io.*; public class App { public static void main(String[] args) { System.setProperty("log4j2.Script.enableLanguages","groovy"); System.setProperty("log4j2.debug","false"); Logger logger = LogManager.getLogger("App"); LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false); File file = new File("log4j2http.xml"); context.setConfigLocation(file.toURI()); logger.debug("Debug log"); } }
目前没有报错,但${result}为空,怀疑是Groovy库导入失败或者脚本本身有错误。开启debug模式后也无法确认脚本是否正确执行,不确定当前这种方法是否可行。
相关日志片段
2022-10-09 00:58:39,198 main DEBUG Installed 2 script engines 2022-10-09 00:58:39,251 main DEBUG Groovy Scripting Engine version: 2.0, language: Groovy, threading: MULTITHREADED, compile: true, names: [groovy, Groovy], factory class: org.codehaus.groovy.jsr223.GroovyScriptEngineFactory 2022-10-09 00:58:39,252 main DEBUG PluginManager 'Core' found 127 plugins 2022-10-09 00:58:39,252 main DEBUG PluginManager 'Level' found 0 plugins 2022-10-09 00:58:39,257 main DEBUG PluginManager 'Lookup' found 16 plugins 2022-10-09 00:58:39,259 main DEBUG Building Plugin[name=Script, class=org.apache.logging.log4j.core.script.Script]. 2022-10-09 00:58:39,278 main DEBUG PluginManager 'TypeConverter' found 26 plugins 2022-10-09 00:58:39,292 main DEBUG createScript(name="selector", language="groovy", scriptText=".................") 2022-10-09 00:58:39,292 main DEBUG Building Plugin[name=scripts, class=org.apache.logging.log4j.core.config.ScriptsPlugin]. 2022-10-09 00:58:39,295 main DEBUG createScripts(={selector}) 2022-10-09 00:58:39,297 main DEBUG Script selector is compilable 2022-10-09 00:58:40,383 main DEBUG Building Plugin[name=KeyValuePair, class=org.apache.logging.log4j.core.util.KeyValuePair]. 2022-10-09 00:58:40,388 main DEBUG KeyValuePair$Builder(key="_Id:", value="{Id:"588"}") 2022-10-09 00:58:40,389 main DEBUG Building Plugin[name=property, class=org.apache.logging.log4j.core.config.Property]. 2022-10-09 00:58:40,389 main DEBUG createProperty(name="Authorization", value="${result}", value="null")
解决方案
问题根源分析
- 脚本变量引用错误:配置中用
${result}获取脚本返回值,但Log4j2脚本不会自动将返回值绑定到result变量,需通过脚本Lookup调用,格式应为${script:selector}(selector是你定义的脚本名称)。 - 脚本未定义变量:Groovy脚本里使用了
key和path但未初始化,执行时会抛出异常,只是Log4j2默认吞了异常,导致返回值为空。 - 脚本执行时机错误:默认
<Script>标签在配置加载时仅执行一次,认证头只会生成一次,后续请求会使用过期的时间戳。
修正步骤
1. 改用动态脚本Lookup,确保每次请求执行脚本
修改HTTP Appender的Authorization属性值:
<Property name="Authorization" value="${script:selector}" />
2. 修复Groovy脚本中的问题
补充变量定义,修正请求方法不一致的问题,调整Base64编码写法:
import org.apache.commons.codec.binary.Base64 import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec // 替换为你的实际密钥和API路径 def id = "XXXXXXXXXXXXXx" def key = "your-real-secret-key" def path = "/rest/log/ingest" Long epoch_time = System.currentTimeMillis() Mac hmac = Mac.getInstance("HmacSHA256") hmac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256")) // 请求方法与实际POST保持一致 def signData = "POST${epoch_time}${path}" byte[] hmacBytes = hmac.doFinal(signData.getBytes("UTF-8")) def signature = Base64.encodeBase64String(hmacBytes) return "${id}:${signature}:${epoch_time}"
3. 确保依赖包齐全
项目依赖需包含:
- Log4j2核心包
- Log4j2 HTTP Appender包
- Groovy脚本引擎包
- Apache Commons Codec包(用于编码)
4. 启用脚本异常日志
在配置中添加脚本日志记录器,方便排查问题:
<Loggers> <Logger name="org.apache.logging.log4j.core.script" level="trace"/> <Root level="trace"> <AppenderRef ref="Http" /> </Root> </Loggers>
替代方案:自定义HTTP Appender插件
如果脚本方式不够灵活,可编写自定义Log4j2 HTTP Appender插件,继承HttpAppender并重写buildRequest方法,在每次请求前动态生成Authorization头,完全控制认证逻辑。
内容的提问来源于stack exchange,提问作者Lizard
相关产品推荐
相关产品推荐

