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

Spring Boot授权服务器中如何记录客户端凭证流成功后的客户端信息

如何在客户端凭证流成功执行时记录RegisteredClient的clientId和clientName信息?

我希望在客户端成功执行客户端凭证流时,记录RegisteredClient.clientId、RegisteredClient.clientName信息,该如何实现?

当前情况

使用客户端ID、客户端密钥等凭证换取访问令牌时,查看TRACE级别日志,仅能看到认证流程的事件描述,未包含clientId、clientName等客户端信息,日志示例如下:

2025-02-26T12:31:25.511+06:00 TRACE 31608 --- [nio-9090-exec-1] o.s.s.authentication.ProviderManager     : Authenticating request with ClientSecretAuthenticationProvider (3/20)
2025-02-26T12:31:25.515+06:00 TRACE 31608 --- [nio-9090-exec-1] s.a.a.ClientSecretAuthenticationProvider : Retrieved registered client
2025-02-26T12:31:25.592+06:00 TRACE 31608 --- [nio-9090-exec-1] s.a.a.ClientSecretAuthenticationProvider : Validated client authentication parameters
2025-02-26T12:31:25.593+06:00 TRACE 31608 --- [nio-9090-exec-1] s.a.a.ClientSecretAuthenticationProvider : Authenticated client secret
2025-02-26T12:31:25.593+06:00 DEBUG 31608 --- [nio-9090-exec-1] o.s.a.w.OAuth2ClientAuthenticationFilter : Set SecurityContextHolder authentication to OAuth2ClientAuthenticationToken

...

2025-02-26T12:31:25.593+06:00 TRACE 31608 --- [nio-9090-exec-1] o.s.security.web.FilterChainProxy        : Invoking OAuth2TokenEndpointFilter (21/25)
2025-02-26T12:31:25.593+06:00 TRACE 31608 --- [nio-9090-exec-1] o.s.s.authentication.ProviderManager     : Authenticating request with OAuth2ClientCredentialsAuthenticationProvider (1/20)
2025-02-26T12:31:25.593+06:00 TRACE 31608 --- [nio-9090-exec-1] 2ClientCredentialsAuthenticationProvider : Retrieved registered client
2025-02-26T12:31:25.593+06:00 TRACE 31608 --- [nio-9090-exec-1] 2ClientCredentialsAuthenticationProvider : Validated token request parameters
2025-02-26T12:31:25.594+06:00 TRACE 31608 --- [nio-9090-exec-1] 2ClientCredentialsAuthenticationProvider : Generated access token
2025-02-26T12:31:25.594+06:00 TRACE 31608 --- [nio-9090-exec-1] 2ClientCredentialsAuthenticationProvider : Saved authorization
2025-02-26T12:31:25.594+06:00 TRACE 31608 --- [nio-9090-exec-1] 2ClientCredentialsAuthenticationProvider : Authenticated token request

希望能将clientId、clientName添加到日志中,通过配置特定类的日志级别即可查看。


实现方案

方案1:扩展OAuth2ClientCredentialsAuthenticationProvider添加自定义日志

Spring Security的OAuth2ClientCredentialsAuthenticationProvider是处理客户端凭证流认证的核心类,继承它并在认证成功后添加日志输出:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;

public class LoggingOAuth2ClientCredentialsAuthenticationProvider extends OAuth2ClientCredentialsAuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(LoggingOAuth2ClientCredentialsAuthenticationProvider.class);

    public LoggingOAuth2ClientCredentialsAuthenticationProvider(RegisteredClientRepository registeredClientRepository) {
        super(registeredClientRepository);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws OAuth2AuthenticationException {
        Authentication result = super.authenticate(authentication);
        if (result.isAuthenticated()) {
            OAuth2ClientCredentialsAuthenticationToken token = (OAuth2ClientCredentialsAuthenticationToken) authentication;
            RegisteredClient registeredClient = getRegisteredClientRepository().findById(token.getClientId());
            // 可根据需求调整日志级别,如TRACE/DEBUG/INFO
            logger.info("客户端凭证流认证成功 - clientId: {}, clientName: {}", registeredClient.getId(), registeredClient.getClientName());
        }
        return result;
    }
}

在Security配置中替换默认Provider:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class OAuth2AuthorizationServerConfig {

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置其他授权服务器逻辑...
        http.getSharedObject(org.springframework.security.authentication.AuthenticationManager.class)
                .authenticationProvider(clientCredentialsAuthenticationProvider(registeredClientRepository()));
        return http.build();
    }

    @Bean
    public OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider(RegisteredClientRepository registeredClientRepository) {
        return new LoggingOAuth2ClientCredentialsAuthenticationProvider(registeredClientRepository);
    }

    // 注册RegisteredClientRepository等其他Bean...
}

最后在日志配置文件(如logback.xml)中设置该类的日志级别:

<logger name="com.yourpackage.LoggingOAuth2ClientCredentialsAuthenticationProvider" level="INFO" />

方案2:监听OAuth2认证成功事件

Spring Security会在认证成功后发布OAuth2AuthenticationSuccessEvent事件,通过事件监听器捕获并提取客户端信息:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationSuccessEvent;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Component;

@Component
public class OAuth2ClientAuthenticationSuccessListener implements ApplicationListener<OAuth2AuthenticationSuccessEvent> {

    private static final Logger logger = LoggerFactory.getLogger(OAuth2ClientAuthenticationSuccessListener.class);
    private final RegisteredClientRepository registeredClientRepository;

    public OAuth2ClientAuthenticationSuccessListener(RegisteredClientRepository registeredClientRepository) {
        this.registeredClientRepository = registeredClientRepository;
    }

    @Override
    public void onApplicationEvent(OAuth2AuthenticationSuccessEvent event) {
        if (event.getAuthentication() instanceof OAuth2ClientCredentialsAuthenticationToken) {
            OAuth2ClientCredentialsAuthenticationToken token = (OAuth2ClientCredentialsAuthenticationToken) event.getAuthentication();
            RegisteredClient registeredClient = registeredClientRepository.findById(token.getClientId());
            logger.info("客户端凭证流认证成功 - clientId: {}, clientName: {}", registeredClient.getId(), registeredClient.getClientName());
        }
    }
}

配置对应类的日志级别即可,这种方式无需修改核心认证逻辑,符合Spring事件驱动设计。

方案3:自定义日志过滤器

在OAuth2TokenEndpointFilter之后添加自定义过滤器,从SecurityContext中获取已认证的客户端信息并记录:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ClientCredentialsLoggingFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(ClientCredentialsLoggingFilter.class);
    private final RegisteredClientRepository registeredClientRepository;

    public ClientCredentialsLoggingFilter(RegisteredClientRepository registeredClientRepository) {
        this.registeredClientRepository = registeredClientRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(request, response);
        
        // 仅针对令牌端点的客户端凭证流请求处理
        if ("/oauth2/token".equals(request.getRequestURI()) && "client_credentials".equals(request.getParameter("grant_type"))) {
            var authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication instanceof OAuth2ClientAuthenticationToken && authentication.isAuthenticated()) {
                OAuth2ClientAuthenticationToken clientToken = (OAuth2ClientAuthenticationToken) authentication;
                RegisteredClient registeredClient = registeredClientRepository.findById(clientToken.getPrincipal().getName());
                logger.info("客户端凭证流获取令牌成功 - clientId: {}, clientName: {}", registeredClient.getId(), registeredClient.getClientName());
            }
        }
    }
}

在Security配置中添加该过滤器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class OAuth2AuthorizationServerConfig {

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        http.addFilterAfter(new ClientCredentialsLoggingFilter(registeredClientRepository()), OAuth2TokenEndpointFilter.class);
        // 其他授权服务器配置...
        return http.build();
    }

    // 注册RegisteredClientRepository等其他Bean...
}

总结

三种方案均可实现需求:

  • **方案2(事件监听)**最轻量化,无需修改核心认证逻辑;
  • 方案1直接扩展认证提供者,日志输出位置更贴近认证成功节点;
  • 方案3通过过滤器实现,适合需要在请求结束后统一记录的场景。

根据实际需求选择即可,配置对应类的日志级别后就能在日志中看到clientId和clientName信息。


内容的提问来源于stack exchange,提问作者Imtiaz Shakil Siddique

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:34:54