You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的无侵入设计模式。

实现步骤

  1. 创建一个Instrumentation模块,拦截Spring Security中认证成功的方法(比如AbstractAuthenticationProcessingFilter.successfulAuthentication)
  2. 在拦截逻辑中提取用户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的初始化方式。

实现步骤

  1. 自定义SpanProcessor Bean,在Span启动时读取Security上下文并添加用户ID
  2. 配置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,但存在以下问题:

  1. 类加载器隔离可能导致类引用异常
  2. 应用启动初期Spring上下文未初始化,会抛出BeanNotFoundException
  3. 不符合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 01:57:38