如何在OpenTelemetry Java Agent扩展中添加Spring认证用户ID到Span属性
解决方案:在OpenTelemetry Java Agent中为HTTP请求Span添加用户ID属性
核心结论
可以实现,但不能直接在Java Agent的SpanProcessor中调用SecurityContextHolder——因为Agent的类加载器与应用类加载器相互隔离,直接引用Spring Security类会触发ClassNotFoundException。以下是两种可行的实现方案:
方案一:通过Instrumentation扩展拦截Spring Security认证流程
这种方式不需要直接访问Spring上下文,而是通过字节码拦截Spring Security的认证成功逻辑,直接将用户ID注入到当前Span中,完全符合Java Agent的无侵入设计模式。
实现步骤
- 创建一个Instrumentation模块,拦截Spring Security中认证成功的方法(比如
AbstractAuthenticationProcessingFilter.successfulAuthentication) - 在拦截逻辑中提取用户ID,获取当前活跃的Span并添加属性
代码示例
public class SpringSecurityAuthInstrumentation { @Advice.OnMethodExit(suppress = Throwable.class) public static void onAuthenticationSuccess(@Advice.Argument(2) Authentication authentication) { if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) { // 提取用户ID(根据你的认证逻辑调整,比如从自定义Principal中获取) String userId = authentication.getName(); // 获取当前OpenTelemetry Span Span currentSpan = GlobalOpenTelemetry.getTracer("spring-security-auth").currentSpan(); if (currentSpan.getSpanContext().isValid()) { currentSpan.setAttribute("userId", userId); } } } public static class AdviceProvider implements InstrumentationModule { @Override public String instrumentationName() { return "spring-security-auth-instrumentation"; } @Override public String[] helperClassNames() { return new String[]{SpringSecurityAuthInstrumentation.class.getName()}; } @Override public void registerAdvice(AdviceRegistry registry) { registry.registerAdvice( TypeDescription.forClass(AbstractAuthenticationProcessingFilter.class), SpringSecurityAuthInstrumentation.class ); } } }
将这个Instrumentation打包到你的自定义OpenTelemetry Java Agent扩展中即可生效。
方案二:使用Spring管理的SpanProcessor(避免类加载器隔离问题)
如果更倾向于使用Spring的Bean管理机制,可以创建一个由Spring托管的SpanProcessor,直接访问SecurityContextHolder,但需要调整OpenTelemetry的初始化方式。
实现步骤
- 自定义SpanProcessor Bean,在Span启动时读取Security上下文并添加用户ID
- 配置OpenTelemetry SDK,将自定义SpanProcessor注册到TracerProvider中
代码示例
自定义SpanProcessor
@Component public class UserIdSpanProcessor implements SpanProcessor { @Override public void onStart(Context parentContext, ReadWriteSpan span) { // 仅处理入站HTTP请求的Span(可通过OTel内置HTTP属性判断) if (span.getAttribute("http.method") != null) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) { String userId = authentication.getName(); span.setAttribute("userId", userId); } } } @Override public boolean isStartRequired() { return true; } @Override public void onEnd(ReadableSpan span) {} @Override public boolean isEndRequired() { return false; } }
OpenTelemetry配置类
@Configuration public class OpenTelemetryConfig { @Bean public OpenTelemetry openTelemetry(UserIdSpanProcessor userIdSpanProcessor) { TracerProvider tracerProvider = TracerProviderSdk.builder() .addSpanProcessor(userIdSpanProcessor) // 可添加其他默认SpanProcessor(如BatchSpanProcessor) .build(); return OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .buildAndRegisterGlobal(); } }
注意事项
- 若使用该方案,需要禁用Java Agent的自动OpenTelemetry初始化,添加JVM参数:
-Dotel.javaagent.enabled=false,避免与Spring配置的实例冲突。 - 如果必须保留Java Agent的其他Instrumentation,可以通过Agent扩展的
SpringBeanAccessor工具类获取Spring Bean,但需注意Spring上下文的初始化时机(启动初期可能无法获取Bean)。
不推荐的方式:直接在Agent SpanProcessor中访问Spring上下文
虽然可以通过Agent提供的SpringBeanAccessor尝试获取Spring Bean,但存在以下问题:
- 类加载器隔离可能导致类引用异常
- 应用启动初期Spring上下文未初始化,会抛出BeanNotFoundException
- 不符合Java Agent的无侵入设计原则
如果仍要尝试,示例代码如下:
public class AgentSideSpanProcessor implements SpanProcessor { @Override public void onStart(Context parentContext, ReadWriteSpan span) { try { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 这里需确保Agent能加载到SecurityContextHolder类,可能需要调整类加载器策略 if (authentication != null) { span.setAttribute("userId", authentication.getName()); } } catch (ClassNotFoundException e) { // 处理类加载异常 } } // 其他方法实现... }
内容的提问来源于stack exchange,提问作者PhilKes
相关产品推荐
相关产品推荐

