SecurityContext#setAuthentication 是否保证可见性?
Does SecurityContext#setAuthentication guaranties visibility?
我在我的项目中使用 spring 安全性。
我有更改登录名的功能。为了实现这个目标,我使用以下代码
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;
我应该用我自己的同步来包装我的代码以实现可见性吗?
P.S.
我已阅读
In an
application which receives concurrent requests in a single session,
the same SecurityContext instance will be shared between threads. Even
though a ThreadLocal is being used, it is the same instance that is
retrieved from the HttpSession for each thread. This has implications
if you wish to temporarily change the context under which a thread is
running. If you just use SecurityContextHolder.getContext(), and call
setAuthentication(anAuthentication) on the returned context object,
then the Authentication object will change in all concurrent threads
which share the same SecurityContext instance. You can customize the
behaviour of SecurityContextPersistenceFilter to create a completely
new SecurityContext for each request, preventing changes in one thread
from affecting another. Alternatively you can create a new instance
just at the point where you temporarily change the context. The method
SecurityContextHolder.createEmptyContext() always returns a new
context instance.
但我不明白 spring 如何保证可见性。刚刚写到会话中的每个线程都会看到变化。但没有答案有多快?更重要的是 - 未解释可见性机制
您的疑虑是有道理的,可见度无法保证。当所有 ThreadLocalMap 的条目都存储相同的对象时,ThreadLocal 不是线程安全的。
参考文档部分 Storing the SecurityContext between requests 警告您这一事实并提出可能的解决方案以某种方式更改上下文,防止影响 其他线程。
此类解决方案的一个示例是 RunAs mechanism,它会在安全对象回调阶段更改上下文。
但是,据我了解您的问题,您需要更改用户的登录名(即用户名)"on the fly"。如果我是对的,那么问题是,当您设置修改后的 Authentication
- 另一个线程可以读取旧值。为避免这种竞争条件,您需要在每次连续登录读取时进行登录写入happens-before。
Authentication
interface has the getPrincipal()
method, which returns an Object
, which is an UserDetails
实例(在大多数情况下)。此对象通常用于获取当前(已验证)用户的用户名。
因此,如果您想更改已验证用户 "on the fly" 的登录名,您可以修改此 UserDetails
对象中的 username
属性。
以线程安全的方式实现它的可能方法是使用 volatile String username
属性 的自定义 UserDetails
实现(默认 User
实现具有不可变的用户名) .
您还应该创建一个 UserDetailsService
实现并将其连接到您的配置中,它将使用您的自定义 UserDetails
.
如果您没有创建新线程或使用 @Async
,那么您在 SecurityContext 上设置新身份验证的方法是正确的。如果您确实创建了新线程,那么它分为两种用例:新线程是在您切换身份验证之前或之后创建的。如果您的线程是在设置新的身份验证对象之后创建的,那么您将必须创建一个线程工厂,该线程工厂可以识别身份验证并将身份验证对象复制到它创建的任何线程。如果您的线程是之前创建的,那么请查看 AuthenticationSuccessEvent
和 AuthenticationEventPublisher
,它们是 Spring 发出身份验证事件信号的官方方式,可能有一些有用的机制来传播身份验证更改跨当前使用该用户的所有线程。
在我以前的一个应用程序中,我必须实现管理员用户可以冒充普通用户的功能,以帮助他们调试应用程序的问题,SecurityContextHolder.getContext().setAuthentication(authentication);
你描述的被使用,我们从未 运行 进入 Spring 混淆它应该以哪个用户身份执行操作的任何问题。多节点集群上的应用程序 运行 具有由所有节点共享的公共会话缓存。
Java 有一套严格且定义明确的规则来规定内存一致性。简而言之,有些事情肯定会 在 其他事情之前发生。你可以阅读它 here。实现 happens-before 的一种方法是通过关键字 volatile
,另一种方法是通过 synchronized
.
与任何文档一样,如果 Spring 没有明确说明它为您提供的保证,那么您不应假设它能保证您想要的。
简而言之,您应该将更改或使用变量的代码包装在 synchronized
块中。这将保证变量的值在您使用完它之前不会改变。 Spring 可能会为您更新变量,但它不能保证跨线程可见性和同步,因为只有您的代码知道您何时以及如何使用该变量。
请注意,您应该使用 synchronized (x)
,其中 x 是全局的、常量的、固定的并且在所有线程之间共享(即 MyClass.class)——而不是变量本身。
我在我的项目中使用 spring 安全性。
我有更改登录名的功能。为了实现这个目标,我使用以下代码
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;
我应该用我自己的同步来包装我的代码以实现可见性吗?
P.S.
我已阅读
In an application which receives concurrent requests in a single session, the same SecurityContext instance will be shared between threads. Even though a ThreadLocal is being used, it is the same instance that is retrieved from the HttpSession for each thread. This has implications if you wish to temporarily change the context under which a thread is running. If you just use SecurityContextHolder.getContext(), and call setAuthentication(anAuthentication) on the returned context object, then the Authentication object will change in all concurrent threads which share the same SecurityContext instance. You can customize the behaviour of SecurityContextPersistenceFilter to create a completely new SecurityContext for each request, preventing changes in one thread from affecting another. Alternatively you can create a new instance just at the point where you temporarily change the context. The method SecurityContextHolder.createEmptyContext() always returns a new context instance.
但我不明白 spring 如何保证可见性。刚刚写到会话中的每个线程都会看到变化。但没有答案有多快?更重要的是 - 未解释可见性机制
您的疑虑是有道理的,可见度无法保证。当所有 ThreadLocalMap 的条目都存储相同的对象时,ThreadLocal 不是线程安全的。
参考文档部分 Storing the SecurityContext between requests 警告您这一事实并提出可能的解决方案以某种方式更改上下文,防止影响 其他线程。
此类解决方案的一个示例是 RunAs mechanism,它会在安全对象回调阶段更改上下文。
但是,据我了解您的问题,您需要更改用户的登录名(即用户名)"on the fly"。如果我是对的,那么问题是,当您设置修改后的 Authentication
- 另一个线程可以读取旧值。为避免这种竞争条件,您需要在每次连续登录读取时进行登录写入happens-before。
Authentication
interface has the getPrincipal()
method, which returns an Object
, which is an UserDetails
实例(在大多数情况下)。此对象通常用于获取当前(已验证)用户的用户名。
因此,如果您想更改已验证用户 "on the fly" 的登录名,您可以修改此 UserDetails
对象中的 username
属性。
以线程安全的方式实现它的可能方法是使用 volatile String username
属性 的自定义 UserDetails
实现(默认 User
实现具有不可变的用户名) .
您还应该创建一个 UserDetailsService
实现并将其连接到您的配置中,它将使用您的自定义 UserDetails
.
如果您没有创建新线程或使用 @Async
,那么您在 SecurityContext 上设置新身份验证的方法是正确的。如果您确实创建了新线程,那么它分为两种用例:新线程是在您切换身份验证之前或之后创建的。如果您的线程是在设置新的身份验证对象之后创建的,那么您将必须创建一个线程工厂,该线程工厂可以识别身份验证并将身份验证对象复制到它创建的任何线程。如果您的线程是之前创建的,那么请查看 AuthenticationSuccessEvent
和 AuthenticationEventPublisher
,它们是 Spring 发出身份验证事件信号的官方方式,可能有一些有用的机制来传播身份验证更改跨当前使用该用户的所有线程。
在我以前的一个应用程序中,我必须实现管理员用户可以冒充普通用户的功能,以帮助他们调试应用程序的问题,SecurityContextHolder.getContext().setAuthentication(authentication);
你描述的被使用,我们从未 运行 进入 Spring 混淆它应该以哪个用户身份执行操作的任何问题。多节点集群上的应用程序 运行 具有由所有节点共享的公共会话缓存。
Java 有一套严格且定义明确的规则来规定内存一致性。简而言之,有些事情肯定会 在 其他事情之前发生。你可以阅读它 here。实现 happens-before 的一种方法是通过关键字 volatile
,另一种方法是通过 synchronized
.
与任何文档一样,如果 Spring 没有明确说明它为您提供的保证,那么您不应假设它能保证您想要的。
简而言之,您应该将更改或使用变量的代码包装在 synchronized
块中。这将保证变量的值在您使用完它之前不会改变。 Spring 可能会为您更新变量,但它不能保证跨线程可见性和同步,因为只有您的代码知道您何时以及如何使用该变量。
请注意,您应该使用 synchronized (x)
,其中 x 是全局的、常量的、固定的并且在所有线程之间共享(即 MyClass.class)——而不是变量本身。