如何在spring-security-oauth2-client:5.5.0中记录并脱敏访问令牌
实现OAuth2客户端凭证令牌响应的日志脱敏记录
方案思路
既然已经定位到org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient#getTokenResponse是令牌接收的核心方法,我们可以通过自定义TokenResponseClient替换默认实现或者AOP切面拦截两种方式,获取令牌响应后完成脱敏再输出日志。
方法一:自定义TokenResponseClient
继承默认实现类,重写令牌获取方法,在返回原响应前先做脱敏日志:
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggingClientCredentialsTokenResponseClient extends DefaultClientCredentialsTokenResponseClient { private static final Logger logger = LoggerFactory.getLogger(LoggingClientCredentialsTokenResponseClient.class); @Override public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRequest grantRequest) { // 获取原始令牌响应 OAuth2AccessTokenResponse originalResponse = super.getTokenResponse(grantRequest); // 脱敏处理:保留首字符,其余替换为* String maskedToken = maskToken(originalResponse.getAccessToken().getTokenValue()); // 构造脱敏后的响应字符串 String maskedResponseStr = String.format( "{\"access_token\": \"%s\", \"expires_in\": %d, \"token_type\": \"%s\"}", maskedToken, originalResponse.getExpiresIn(), originalResponse.getTokenType().getValue() ); // 打印日志 logger.info("客户端凭证令牌响应(脱敏后):{}", maskedResponseStr); return originalResponse; } private String maskToken(String token) { if (token == null || token.length() <= 1) { return token; } return token.charAt(0) + "*".repeat(token.length() - 1); } }
然后配置Spring容器替换默认Bean:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.endpoint.ClientCredentialsTokenResponseClient; @Configuration public class OAuth2ClientConfig { @Bean public ClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient() { return new LoggingClientCredentialsTokenResponseClient(); } }
方法二:AOP切面拦截
如果不想修改原有Bean逻辑,直接用AOP拦截目标方法的返回结果:
- 先确保项目引入Spring AOP依赖(Maven示例):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
- 编写切面类:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.stereotype.Component; @Aspect @Component public class TokenResponseLoggingAspect { private static final Logger logger = LoggerFactory.getLogger(TokenResponseLoggingAspect.class); // 拦截目标方法的返回值 @AfterReturning(pointcut = "execution(org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient.getTokenResponse(..))", returning = "response") public void logTokenResponse(JoinPoint joinPoint, OAuth2AccessTokenResponse response) { String maskedToken = maskToken(response.getAccessToken().getTokenValue()); String maskedResponseStr = String.format( "{\"access_token\": \"%s\", \"expires_in\": %d, \"token_type\": \"%s\"}", maskedToken, response.getExpiresIn(), response.getTokenType().getValue() ); logger.info("客户端凭证令牌响应(脱敏后):{}", maskedResponseStr); } private String maskToken(String token) { if (token == null || token.length() <= 1) { return token; } return token.charAt(0) + "*".repeat(token.length() - 1); } }
注意事项
- 脱敏规则可按需调整:比如保留前3位、固定显示10个*,或是只保留首尾字符等。
- 日志级别可根据场景切换为
debug或info。 - 若需记录
scope等额外字段,直接在构造脱敏字符串时补充对应内容即可。
内容的提问来源于stack exchange,提问作者Victor Levin
相关产品推荐
相关产品推荐

