SecurityContext#setAuthentication是否保证可见性?

时间:2017-09-19 14:18:56

标签: java multithreading spring-security thread-safety visibility

我在项目中使用spring security。

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

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。

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

  

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

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

3 个答案:

答案 0 :(得分:7)

您的疑虑是合理的,可见性不是保证。当所有ThreadLocalMap的条目存储同一个对象时,ThreadLocal不是线程安全的。

参考文档部分Storing the SecurityContext between requests向您发出警告,并提出可能的解决方案,以某种方式更改上下文,阻止对其他线程的影响

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

但是,据我了解您的问题,您需要动态更改用户的登录信息(即用户名)""。如果我是对的,那么问题是,当您设置修改后的Authentication时,另一个线程可以读取旧值。要避免此竞争条件,您需要在每次顺序登录读取时进行登录 happens-before

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

因此,如果您想要更改已通过身份验证的用户的登录信息"您可以修改此username对象中的UserDetails属性。

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

您还应该创建UserDetailsService实施并将其与您的配置相结合,该实施将使用您的自定义UserDetails

答案 1 :(得分:0)

如果您没有创建新线程或使用from PyQt5.QtWidgets import QApplication, QMainWindow import sys class A(): def closeEvent(self,event): print("A") class B(QMainWindow): def closeEvent(self,event): print("B") super().closeEvent(event) class C(A,B): pass class D(B,A): pass class E(QMainWindow,A): pass class F(A,QMainWindow): pass def test(TestClass, msg): """Create class instance and show it. Click on cross to close.""" print(msg) app = QApplication(sys.argv) test = TestClass() test.show() app.exec() test(C,"C(A,B)?") # >>> A test(D,"D(B,A)?") # >>> B test(E,"E(QMainWindow,A)?") # >>> A ??? Why ??? test(F,"F(A,QMainWindow)?") # >>> A ,那么在SecurityContext上设置新身份验证的方法是正确的。如果您确实创建了新线程,那么它分为两个用例:在切换身份验证之前或之后创建新线程。如果在设置新的身份验证对象后创建了线程,则必须创建一个可识别身份验证的线程工厂,并将Authentication对象复制到它创建的任何线程。如果您之前创建了线程,那么请查看@AsyncAuthenticationSuccessEvent,它们是Spring发出auth事件信号的官方方式,可能有一些有用的机制可以在当前使用的所有线程中传播身份验证更改用户。

在我之前的一个应用程序中,我必须实现管理员用户可以模拟常规用户的功能,以帮助他们调试应用程序的问题,使用了您描述的AuthenticationEventPublisher并且我们从未遇到过Spring的任何问题混淆哪个用户应该执行操作。该应用程序在多节点群集上运行,并且所有节点共享一个公共会话缓存。

答案 2 :(得分:0)

Java有一套严格且定义明确的规则来规定内存一致性。简而言之,有些事情可以保证之前发生。你可以阅读它here。通过关键字volatile实现之前发生的方法之一是通过synchronized实现

与任何文档一样,如果Spring没有明确说明它为您提供什么保证,那么您不应该认为它保证了您的需求。

简而言之,您应该在synchronized块中包装更改或使用变量的代码。这将保证变量的值在完成使用之前不会更改。 Spring可能会为您更新变量,但它不能保证跨线程可见性和同步,因为只有您的代码知道您何时以及如何使用该变量。

注意你应该使用synchronized (x),其中x是所有线程之间的全局,常量,固定和共享(即MyClass.class) - 而不是变量本身。