2017-05-31 56 views
2

我有一個使用二進制websocket(即NO Stomp,AMQP純二進制緩衝區)的spring boot(1.5.2.RELEASE)應用程序。在我的測試中,我能夠來回發送消息,這非常棒。在春季啓動測試中純二進制websocket連接期間持久TestSecurityContextHolder

但是,在websocket調用應用程序期間,我遇到了與TestSecurityContexHolder相關的以下無法解釋的行爲。

TestSecurityContextHolder的上下文已開始正確設置,即我的客戶@WithMockCustomUser正在設置它,我可以驗證在測試開始時放置breankpoint時的情況。即

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>, 

這偉大工程,我能夠測試實現方法級別的安全性,如

@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") 
public UserInterface get(String userName) { 
… 
} 

我已經開始遇到的問題是服務器端方法,當我想要做的完全集成測試應用程序,即在測試中,我創建了自己的WebSocket連接到應用程序,只使用java特定的註釋(即客戶端沒有註釋)。

ClientWebsocketEndpoint clientWebsocketEndpoint = new ClientWebsocketEndpoint(uri); 

@ClientEndpoint 
public class ClientWebsocketEndpoint { 


    private javax.websocket.Session session = null; 

    private ClientBinaryMessageHandler binaryMessageHandler; 
    ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 

    public ClientWebsocketEndpoint(URI endpointURI) { 
     try { 
      WebSocketContainer container = ContainerProvider.getWebSocketContainer(); 
      container.connectToServer(this, endpointURI); 


     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 
    …. 
} 

如果嘗試調用WebSocket的話,我第一次看到「SecurityContextPersistenceFilter」是消除當前SecurityContex這是完全可以預料。我真的希望它得到刪除,因爲我想測試身份驗證,因爲身份驗證是websocket通信的一部分,而不是我的情況下的http調用的一部分,但困擾我的是以下內容。 到目前爲止,我們只有一個HTTP調用(wireshark證明了這一點),而SecurityContextPersistenceFilter只清除了一次會話,並通過在clear方法上設置一個斷點,我發現它確實只被調用過一次。 6個二進制傳遞消息後(即SecurityContext的在從客戶端接收到的5消息中設置)被在客戶端和服務器之間交換我認證與自定義的令牌和寫該令牌到TestSecurityContextHolder順便說一句SecurityContexHolder即

 SecurityContext realContext = SecurityContextHolder.getContext(); 
     SecurityContext testContext = TestSecurityContextHolder.getContext(); 

      token.setAuthenticated(true); 

      realContext.setAuthentication(token); 
      testContext.setAuthentication(token); 

我看到該令牌的hashCode與購買的ContexHolders中的相同,這意味着這是相同的對象。但是,下次我從客戶端收到一個ByteBuffer時,SecuriyContextHolder.getAuthentication()的結果爲null。我首先認爲他與SecurityContextChannelInterceptor有關,因爲我讀了一篇關於websocket和spring的好文章,即here,但似乎並不是這樣。至少在放置斷點時,securityContextChannelInterceptor不會被執行或調用,我發現IDE並沒有在那裏停止。請注意,我故意不在這裏擴展AbstractWebSocketMessageBrokerConfigurer,因爲我不需要它,即這是簡單的二進制websocket,沒有(STOMP AMQP等,即沒有已知的消息傳遞)。但是我看到另一個階級即WithSecurityContextTestExecutionListener清除上下文

TestSecurityContextHolder.clearContext() line: 67 
WithSecurityContextTestExecutionListener.afterTestMethod(TestContext) line: 143 
TestContextManager.afterTestMethod(Object, Method, Throwable) line: 319 
RunAfterTestMethodCallbacks.evaluate() line: 94 

但只有當測試完成!即在SecurityContext爲空之後,儘管之前手動設置了客戶令牌。看起來像過濾器(但對於websockets即不是HTTP)正在清除接收到的每個WsFrame上的securityContext。我不知道那是什麼。也可能是相對的是:在服務器端,當我看到堆棧跟蹤時,我可以觀察到正在調用StandardWebSocketHandlerAdapter,它正在創建StandardWebSocketSession。

StandardWebSocketHandlerAdapter$4.onMessage(Object) line: 84  
WsFrameServer(WsFrameBase).sendMessageBinary(ByteBuffer, boolean) line: 592 

在StandardWebSocketSession中,我看到有一個字段「Principal user」。那麼誰應該設置該主體,即我沒有看到任何設置方法,那麼設置它的唯一方法是在「AbstractStandardUpgradeStrategy」期間,即在第一次調用期間,但是一旦建立會話後該怎麼做?即rfc6455定義爲

10.5。 WebSocket客戶端身份驗證 此協議沒有規定服務器可以在WebSocket握手期間對客戶端進行身份驗證的任何特定方式。 WebSocket 服務器可以使用任何可用的客戶端身份驗證機制

對我來說,這意味着我應該能夠在後續階段定義用戶主體,只要我想要。

這裏是如何測試拼命地跑

@RunWith(SpringRunner.class) 
@TestExecutionListeners(listeners={ // ServletTestExecutionListener.class, 
            DependencyInjectionTestExecutionListener.class, 
            TransactionalTestExecutionListener.class, 
            WithSecurityContextTestExecutionListener.class 
            } 
     ) 
@SpringBootTest(classes = { 
          SecurityWebApplicationInitializerDevelopment.class, 
          SecurityConfigDevelopment.class, 
          TomcatEmbededDevelopmentProfile.class, 
          Internationalization.class, 
          MVCConfigDevelopment.class, 
          PersistenceConfigDevelopment.class 
}) 

@WebAppConfiguration 
@ActiveProfiles(SConfigurationProfiles.DEVELOPMENT_PROFILE) 
@ComponentScan({ 
    "org.Server.*", 
    "org.Server.config.*", 
    "org.Server.config.persistence.*", 
    "org.Server.core.*", 
    "org.Server.logic.**", 

}) 


@WithMockCustomUser 
public class workingWebSocketButNonWorkingAuthentication { 
.... 

這裏是部分之前

@Before 
public void setup() { 

    System.out.println("Starting Setup"); 

    mvc = MockMvcBuilders 
      .webAppContextSetup(webApplicationContext) 
      .apply(springSecurity()) 
      .build(); 

    mockHttpSession = new MockHttpSession(webApplicationContext.getServletContext(), UUID.randomUUID().toString()); 

} 

而且爲了總結一下我的問題是,這可能是導致那裏的安全上下文從返回的行爲從客戶端收到另一個ByteBuffer(WsFrame)後,是否購買了TestSecurityContextHolder或SecurityContextHolder爲null?

@Added 31 May: 我在運行多次測試時發現,有時候這個contex不爲空,並且測試OK,即有時候這個contex確實是用我提供的令牌填充的。我想這與Spring Security身份驗證綁定到ThreadLocal這一事實有關,需要進一步挖掘。

@添加2017年6月6日: 我可以確認知道問題出在線程中認證成功但在http-nio-8081-exec-4跳轉到nio-8081-exec-5時,安全Contex正在迷失,那是在我已經將SecurityContextHolder策略設置爲MODE_INHERITABLETHREADLOCAL的情況下。任何sugesstions非常感謝。

已添加2017年六月7日
如果添加了SecurityContextPropagationChannelInterceptor,在簡單websocket的情況下不會傳播安全上下文。

@Bean 
@GlobalChannelInterceptor(patterns = {"*"}) 
public ChannelInterceptor securityContextPropagationInterceptor() 
{ 
    return new SecurityContextPropagationChannelInterceptor(); 
} 

新增2017年6月12日
做了測試與異步符號即一個在這裏找到。 spring-security-async-principal-propagation。這表明安全上下文在Spring中在不同線程中執行的方法之間正確傳輸,但由於某些原因,同樣的事情對於Tomcat線程不起作用,例如http-nio-8081-exec-4,http-nio- 8081-exec-5,http-nio-8081-exec-6,http-nio-8081-exec-7等。我覺得他與執行者有關係,但到目前爲止我不知道如何改變那。

添加13 2017年

我已經打印出當前線程和安全Contex的發現,六月的第一個線程,即使用HTTP-NIO-8081-EXEC-1確實有填充按模式預期,即安全上下文MODE_INHERITABLETHREADLOCAL,但是所有進一步的線程,即http-nio-8081-exec-2,http-nio-8081-exec-3都沒有。現在的問題是:這是預期的嗎?我在這裏發現working with threads in Spring

您不能在同級線程之間(例如在線程池中)共享安全上下文。此方法僅適用於已包含已填充的SecurityContext的線程產生的子線程。

基本上解釋它,但是因爲在Java中沒有辦法找出線程的父親,我想問題是誰在創建線程http-nio-8081-exec-2,是調度程序servlet或是該tomcat以某種方式神奇地決定現在我將創建一個新的線程。我問,因爲我看到有時部分代碼在同一個線程中執行,或者根據運行而不同。

添加14 2017年六月

,因爲我不希望把所有在一個我創建了一個分開的問題與找到了答案,如何將安全上下文傳播到由創建的所有兄弟線程的問題涉及Tomcat的情況下,春季啓動應用程序。發現here

回答

4

我不是100%確定我明白這個問題,但是Java調度程序servlet不會在不被告知的情況下創建新線程。我認爲tomcat處理不同線程中的每個請求,所以這可能是創建線程的原因。你可以查詢thisthis。祝你好運!

+0

感謝您的意見。它似乎像tomcat創建這些線程。我目前正在尋找正確的方法來確保安全上下文在線程之間保持同步,即確保對象(安全上下文)在這些線程之間保持同步我不確定什麼確切地使用ReentrantLock CountDownLatch,Semaphore,監視器,你知道是否有什麼彈簧建議已經? – Tito

+0

我不確定取決於情況,我認爲ReentrantLock將是您最好的選擇。同步甚至可能工作。 – user2309843

+0

由於春天有DelegatingSecurityContextRunnable,但我不知道如何使用它來包裝tomcat線程的目標是同步的兄弟線程之間的SecurityContex所以我想我會去與ReentrantLock,除非我找到答案如何做到這一點與DelegatingSecurityContextRunnable – Tito