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

如何在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拦截目标方法的返回结果:

  1. 先确保项目引入Spring AOP依赖(Maven示例):
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 编写切面类:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 18:45:15