Spring引导测试中纯二进制websocket连接期间的Perserving TestSecurityContextHolder

Spring引导测试中纯二进制websocket连接期间的Perserving TestSecurityContextHolder,spring,spring-boot,junit,spring-security,Spring,Spring Boot,Junit,Spring Security,我有一个springboot(1.5.2.RELEASE)应用程序,它使用二进制websocket(即NO-Stomp,AMQP纯二进制缓冲区)。在我的测试中,我能够来回发送消息,效果非常好 但是,在websocket调用应用程序期间,我遇到了以下与TestSecurityContexHolder相关的无法解释的行为 TestSecurityContextHolder有一个正确设置开始的上下文,即my customer@WithMockCustomUser正在设置它,我可以在测试开始时设置一个b

我有一个springboot(1.5.2.RELEASE)应用程序,它使用二进制websocket(即NO-Stomp,AMQP纯二进制缓冲区)。在我的测试中,我能够来回发送消息,效果非常好

但是,在websocket调用应用程序期间,我遇到了以下与TestSecurityContexHolder相关的无法解释的行为

TestSecurityContextHolder有一个正确设置开始的上下文,即my customer@WithMockCustomUser正在设置它,我可以在测试开始时设置一个breankpoint来验证这一点。即

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>, 
我开始遇到的问题是,当我想对应用程序进行完全集成测试时,即在测试中,我将自己的WebSocket连接打包到应用程序,只使用java特定的注释,即(客户端中没有spring注释)

如果尝试调用websocket,那么我首先看到“SecurityContextPersistenceFilter”正在删除当前的SecurityContex,这是完全预期的。实际上,我希望它被删除,因为我想测试身份验证,因为身份验证是websocket通信的一部分,而不是我的http调用的一部分,但困扰我的是以下内容。 到目前为止,我们只有一个HTTP调用(wireshark证明了这一点),SecurityContextPersistenceFilter只清除了一次会话,通过在clear方法上设置断点,我发现它确实只被调用了一次。在客户端和服务器之间交换6条二进制消息(即,从客户端接收的5条消息中设置了SecurityContext)后,我使用自定义令牌进行身份验证,并将该令牌写入TestSecurityContextHolder btw SecurityContextHolder,即

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

            token.setAuthenticated(true);

            realContext.setAuthentication(token);
            testContext.setAuthentication(token);
我看到该令牌的hashCode在购买的ContexHolders中是相同的,这意味着这是相同的对象。但是,下次我从客户端收到ByteBuffer时,SecurityContextHolder.getAuthentication()的结果为null。首先,我认为他与SecurityContextChannelInterceptor有关,因为我读了一篇关于websockets和spring的好文章,但事实似乎并非如此。securityContextChannelInterceptor不会在任何地方执行或调用,至少在放置断点时,我看到IDE不会停止在那里。请注意,我故意不在这里扩展AbstractWebSocketMessageBrokerConfiguration,因为我不需要它,也就是说,这是一个简单的二进制websocket,没有(STOMP AMQP等,也就是说,没有已知的消息)。但是,我看到另一个类,即使用SecurityContextTestExecutionListener清除上下文

TestSecurityContextHolder.clearContext() line: 67   
WithSecurityContextTestExecutionListener.afterTestMethod(TestContext) line: 143 
TestContextManager.afterTestMethod(Object, Method, Throwable) line: 319 
RunAfterTestMethodCallbacks.evaluate() line: 94 
但只有当测试完成时!!!i、 e.这是SecurityContext为null之后的方式,尽管之前手动设置了客户令牌。看起来像是一个过滤器(但对于WebSocket,即不是HTTP)正在清除接收到的每个WsFrame上的securityContext。我不知道那是什么。还有一个可能是相对的:在服务器端,当我看到堆栈跟踪时,我可以观察到正在调用StandardWebSocketHandlerAdapter,它正在创建StandardWebSocketSession

StandardWebSocketHandlerAdapter$4.onMessage(Object) line: 84    
WsFrameServer(WsFrameBase).sendMessageBinary(ByteBuffer, boolean) line: 592 
在StandardWebSocketSession中,我看到有一个字段“Principal user”。那么,应该由谁来设置主体,即,我没有看到任何设置方法。设置主体的唯一方法是在“AbstractStandardUpgradeStrategy”期间,即在第一次调用中,但一旦会话建立,该怎么办?i、 e.rfc6455定义了

10.5。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返回的安全上下文为空时,是什么导致了这种行为

@5月31日增补: 我在多次运行测试时偶然发现,有时contex不为null,而测试正常,即有时contex确实填充了我提供的令牌。我想这与Spring安全认证绑定到ThreadLocal这一事实有关,需要进一步挖掘

@2017年6月6日新增: 我可以确认我知道问题出在线程中,即身份验证成功,但当在http-nio-8081-exec-4到nio-8081-exec-5之间跳转时,安全上下文正在丢失,这就是我将SecurityContextHolder策略设置为模式_INHERITABLETHREADLOCAL的情况。非常感谢您的任何建议

2017年6月7日新增
如果我添加SecurityContextPropagationChannelInterceptor,那么对于简单websocket,它不会传播安全上下文

@Bean
@GlobalChannelInterceptor(patterns = {"*"})
public ChannelInterceptor securityContextPropagationInterceptor()
{
    return new SecurityContextPropagationChannelInterceptor();
}
2017年6月12日新增
使用异步表示法进行测试,即在此处找到的表示法。这表明在spring中不同线程中执行的方法之间正确地传输了安全上下文,但出于某种原因,同样的事情不适用于Tomcat线程,即http-nio-8081-exec-4、http-nio-8081-exec-5、http-nio-8081-exec-6、,http-nio-8081-exec-7等。我感觉他的行为与执行人有关,但到目前为止我不知道如何改变这一点

2017年6月13日增补

通过打印当前线程和安全上下文,我发现第一个线程,即http-nio-8081-exec-1,确实按照预期填充了安全上下文,即每个模式模式_INHERITABLETHREADLOCAL,但是所有其他线程,即http-nio-8081-exec-2、http-nio-8081-exec-3,都没有。现在的问题是
@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());

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