服务提供者如何获取当前用户
问题
项目中使用了 Dubbo 实现服务间调用,使用 Spring Security Oauth2 实现授权和鉴权。在服务消费者调用服务提供者的时候,服务提供者无法通过SecurityContextHolder.getContext().getAuthentication()获取当前用户信息。
问题分析
为什么在使用 Dubbo 调用时,服务提供者无法使用SecurityContextHolder.getContext().getAuthentication()获取当前用户信息,要搞清楚这个问题,先分析获取当前用户信息的过程。
获取用户信息的过程
方法SecurityContextHolder.getContext().getAuthentication()是通过调用SecurityContextHolder里面的strategy属性的getContext方法获取SecurityContext实例,然后再通过SecurityContext的getAuthentication获取用户信息的。
SecurityContextHolderStrategy是一个接口类,它的实现类如下:

它一共有四种实现类,那么在应用中,它使用的是哪一种实现类,先看下SecurityContextHolder。
SecurityContextHolder是将给定的SecurityContext与当前执行线程关联起来,获取和设置SecurityContext的方式是委托给strategy属性:
/**
* 1. 首先会获取 JVM 的 spring.security.strategy 属性
* 2. 在类加载的时候会调用一次 initialize 方法,进行初始化
* 3. 根据 spring.security.strategy 属性,采用不同的实现类初始化 strategy 属性
* 4. 如果没有设置 spring.security.strategy 属性,则默认使用 MODE_THREADLOCAL 策略
* 5. MODE_THREADLOCAL 策略,strategy 则是使用 ThreadLocalSecurityContextHolderStrategy 进行初始化
**/
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
static {
initialize();
}
private static void initialize() {
initializeStrategy();
// 初始化次数 +1
initializeCount++;
}
private static void initializeStrategy() {
if (MODE_PRE_INITIALIZED.equals(strategyName)) {
Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
+ ", setContextHolderStrategy must be called with the fully constructed strategy");
return;
}
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
return;
}
if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
return;
}
if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
return;
}
// Try to load a custom strategy
try {
// 我们也可以自定义实现策略,扩展性+++
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
看下ThreadLocalSecurityContextHolderStrategy类,可以发现这个类是通过一个ThreadLocal属性存储和获取SecurityContext:
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
再看看接口SecurityContextHolderStrategy,可以发现它提供了setContext方法来设置SecurityContext,所以在使用SecurityContextHolder.getContext().getAuthentication()获取的SecurityContext就是通过setContext方法设置的值,如果没有手动指定其他策略的话,也就是org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy#contextHolder存储的值,看下SecurityContext的实现类,可以发现用户信息是通过org.springframework.security.core.context.SecurityContextImpl#getAuthentication方法获取的,而获取的用户信息是SecurityContext使用方法setAuthentication设置的,设置的用户信息又是从哪来的呢?
SecurityContext的authentication是从哪来的
在上面的分析中,可知 Spring Security 是如何设置的authentication,在此打个断点:

再跟踪下堆栈信息:

在此可以发现用户信息是在方法:org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter中获取和设置的,在这个方法中,关键在于这段代码:
Authentication authResult = authenticationManager.authenticate(authentication);
这段代码验证并获取了用户信息,在分析这段代码之前,可以先留意下这个方法中的这段代码:request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());。
在authenticationManager.authenticate打个断点,跟踪下去,可以发现在方法org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager#authenticate中获取了用户信息:

到此可知获取用户信息的流程,从authentication.getPrincipal()