Spring Boot授权服务器中如何记录客户端凭证流成功后的客户端信息
我希望在客户端成功执行客户端凭证流时,记录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

