SecurityContext#setAuthentication 是否保证可见性?

我在我的项目中使用弹簧安全性。

我有更改登录的功能。为了实现这个目标,我使用以下代码

Authentication authentication = ...
SecurityContextHolder.getContext().setAuthentication(authentication);

但是现在我正在详细地研究此代码,并看到身份验证字段不是因此不能保证可见性:volatile

 public class SecurityContextImpl implements SecurityContext {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // ~ Instance fields
    // ================================================================================================

    private Authentication authentication;

我是否应该使用自己的同步来包装代码以实现可见性?

附言

我已阅读 https://stackoverflow.com/a/30781541/2674303

在单个会话中接收并发请求的应用程序中,将在线程之间共享相同的 SecurityContext 实例。即使正在使用 ThreadLocal,它也是从 HttpSession 中为每个线程检索的同一实例。如果您希望临时更改线程运行的上下文,这将产生影响。如果您只使用 SecurityContextHolder.getContext(),并在返回的上下文对象上调用 setAuthentication(anAuthentication),则身份验证对象将在共享同一 SecurityContext 实例的所有并发线程中更改。您可以自定义 SecurityContextPersistenceFilter 的行为,为每个请求创建一个全新的 SecurityContext,从而防止一个线程中的更改影响另一个线程。或者,您可以在临时更改上下文时创建新实例。方法 SecurityContextHolder.createEmptyContext() 始终返回一个新的上下文实例。

但我不明白春天如何保证能见度。刚刚写到会话中的每个线程都将看到更改。但是没有答案有多快?更重要的是 - 可见性机制没有解释


答案 1

你的怀疑是有道理的,知名度是不能保证。ThreadLocal 不是线程安全的,当所有 ThreadLocalMap 的条目都存储同一个对象时。

引用的文档部分“在请求之间存储 SecurityContext”会警告您这一事实,并提出可能的解决方案,以以某种方式更改上下文,从而防止对其他线程的影响。

此类解决方案的一个示例是 RunAs 机制,该机制在安全对象回调阶段更改上下文。

但是,正如我理解您的问题一样,您需要“动态”更改用户的登录名(即用户名)。如果我是对的,那么问题是,当你设置一个修改后 - 另一个线程可以读取旧值。为避免此争用情况,您需要在每次顺序登录读取之前进行登录写入。Authentication

身份验证接口具有 getPrincipal() 方法,该方法返回一个 ,它是 UserDetails 实例(在大多数情况下)。此对象通常用于获取当前(经过身份验证的)用户的用户名。Object

因此,如果要“动态”更改经过身份验证的用户的登录名,可以修改此对象中的属性。usernameUserDetails

以线程安全的方式实现它的可能方法是使用该属性的自定义实现(默认User实现具有不可变的用户名)。UserDetailsvolatile String username

您还应该创建一个 UserDetailsService 实现并将其连接到您的配置中,该实现将使用您的自定义 .UserDetails


答案 2

如果您没有创建新线程或使用 ,那么您在 SecurityContext 上设置新身份验证的方法是正确的。如果您确实要创建新线程,则它分为两种用例:在切换身份验证之前或之后创建新线程。如果线程是在设置新的身份验证对象后创建的,则必须创建一个可识别身份验证的线程工厂,并将 Authentication 对象复制到它创建的任何线程。如果你的线程是之前创建的,那么看看并且,它们是Spring向身份验证事件发出信号的官方方式,可能有一些有用的机制可以在该用户当前使用的所有线程中传播身份验证更改。@AsyncAuthenticationSuccessEventAuthenticationEventPublisher

在我以前的一个应用程序中,我必须实现管理用户可以模拟普通用户的功能,以帮助他们调试应用程序的问题,您描述的是使用,我们从未遇到过Spring的任何问题,令人困惑的是哪个用户应该执行操作。应用在多节点群集上运行,该群集具有由所有节点共享的公共会话缓存。SecurityContextHolder.getContext().setAuthentication(authentication);