Reassign TestSecurityContextHolder while connecting to pure binary websocket in Spring Boot Test

I have a spring boot application (1.5.2.RELEASE) that uses a binary websocket (i.e. NO Stomp, pure AMQP binary buffer). In my test, I can send messages back and forth that work great.

However, I experience the following inexplicable behavior related to TestSecurityContexHolder during websocket application calls.

TestSecurityContextHolder has a context that starts correctly, for example, my client @WithMockCustomUser installs it, and I can check it when you set the breakpoint at the beginning of the test. I.e.

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>, 

This works fine, and I can test server-side methods that implement method level security, like

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

The problem I'm starting to experience is when I want to perform a full integration test of the application, i.e. as part of the test, I bind my own WebSocket connection to the application using only specific java annotations, i.e. (no spring annotaions per client).

 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); } } …. } 

If you try to call websocket, I will first see that "SecurityContextPersistenceFilter" deletes the current SecurityContex, which is fully expected. I actually want it to be deleted, since I still want to test authentication, since authentication is part of the exchange via websocket, and not part of the http call in my case, but the following worries me. So far, we have had only one HTTP call (wireshark proves this), and SecurityContextPersistenceFilter cleared the session only once and set a breakpoint on an understandable method, I see that it really was called only once. After exchanging 6 binary messages (i.e. SecurityContext in the 5th message received from the client), the client and server authenticate using a user token and write this token to the TestSecurityContextHolder test system to ensure SecurityContexHolder, i.e.

  SecurityContext realContext = SecurityContextHolder.getContext(); SecurityContext testContext = TestSecurityContextHolder.getContext(); token.setAuthenticated(true); realContext.setAuthentication(token); testContext.setAuthentication(token); 

I see that the hash code of this token is the same in the purchased ContexHolders, which means that it is the same object. However, the next time I received a ByteBuffer from a client, the result of SecuriyContextHolder.getAuthentication () is null. First I want it to be related to SecurityContextChannelInterceptor, since I read a good article on websites and spring i.e. here , but it does not seem so. SecurityContextChannelInterceptor is not running or is not called anywhere, at least when setting breakpoints, I see that the IDE does not stop there. Please note that I intentionally do not distribute AbstractWebSocketMessageBrokerConfigurer here, since I do not need it, i.e. This is a simple simple binary websocket without (STOMP AMQP, etc., T.E. Unknown messaging). However, I see another class, i.e. WithSecurityContextTestExecutionListener clearing the context

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

but only after completion of the test !!! those. this is the path after the SecurityContext is null, although manually set with the client token earlier. It seems like something like a filter (but for web sockets, not HTTP) clears the securityContext of every received WsFrame. I have no idea what that is. It can also be relative: on the server side, when I see the stack trace, I can notice that the StandardWebSocketHandlerAdapter is called, which creates a StandardWebSocketSession.

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

In StandardWebSocketSession, I see that there is a "Primary User" field. Well, who should establish this principle, i.e. I do not see any installed methods, the only way to set it is during "AbstractStandardUpgradeStrategy", i.e. At the first call, but then what to do after it has been installed? those. rfc6455 identified

10.5. WebSocket Client Authentication This protocol does not specify any specific way that clients can authenticate during a WebSocket handshake. WebSocket server can use any available client authentication mechanism

for me, this means that I MUST be able to identify the Principal user at a later stage when I want.

here's how to test.

 @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 { .... 

here is the previous part

 @Before public void setup() { System.out.println("Starting Setup"); mvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .apply(springSecurity()) .build(); mockHttpSession = new MockHttpSession(webApplicationContext.getServletContext(), UUID.randomUUID().toString()); } 

And in order to generalize my question, there may be a behavior in which the Security Context returned from the purchased TestSecurityContextHolder or SecurityContextHolder is null after another ByteBuffer (WsFrame) client is received from the client ?.

@ Added on May 31: By coincidence when starting the mulitple test time, sometimes the context is not null and the test OK, that is, sometimes the context is really filled with the entered token. I assume that this is due to the fact that spring Security authentication is tied to ThreadLocal, further digging will be required.

@ Added on June 6, 2017: I can confirm that the problem is that authentication was successful, but when switching between http-nio-8081-exec-4 and nio-8081-exec-5, Contex security is lost and this happens in the case when I set the SecurityContextHolder strategy to MODE_INHERITABLETHREADLOCAL. Any sugesstions are welcome.

Added June 7, 2017
If I add that SecurityContextPropagationChannelInterceptor will not propagate the Security Context in the case of a simple websocket.

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

Added June 12, 2017
made a test with asynchronous notation, that is, found here. spring-security-async-principal-propagation . This shows that the security context is correctly passed between methods that execute on different threads in spring, but for some reason the same thing doesn't work for Tomcat threads, i.e. http-nio-8081-exec-4, http-nio -8081-exec-5, http-nio-8081-exec-6, http-nio-8081-exec-7, etc. I have a feeling that he has something to do with the artist, but so far I don’t know how to change it.

Added June 13, 2017

I found, by printing the current threads and Security Contex, that the very first thread, i.e. http-nio-8081-exec-1 has a security context populated as expected, i.e. in MODE_INHERITABLETHREADLOCAL mode, however, all subsequent streams, that is, http-nio-8081-exec-2, http-nio-8081-exec-3, are not. Now the question is: was this expected? I found here threading in Spring's statement that

You cannot share a security context between sibling threads (for example, in a thread pool). This method only works for child threads that are spawned by a thread that already contains a populated SecurityContext.

which basically explains this, however, since in Java there is no way to find out the parent of the thread, I think the question of who creates Thread-http-nio-8081-exec-2 is whether the dispatcher servlet is or what tomcat is somehow magically decides now, I will create a new thread. I ask about it because I see that sometimes part of the code is executed in the same thread or in different ones depending on the launch.

Added June 14, 2017

Since I do not want to put everything in one, I created a separate question, which concerns the problem of finding an answer to the question of how to distribute the security context for all sibling threads created by tomcat in case of spring application loading. found here

+2
source share
1 answer

I am not 100% sure, I understand the problem, but it is unlikely that the Java dispatcher servlet will create a new thread without saying so. I think tomcat processes each request in a different thread, so it is possible that threads are being created. You can check it out and this one . Good luck

+4
source

Source: https://habr.com/ru/post/1268838/


All Articles