在Spring Boot测试中的纯二进制websocket连接期间保留TestSecurityContextHolder [英] Perserving TestSecurityContextHolder during pure binary websocket connection in Spring Boot test

查看:135
本文介绍了在Spring Boot测试中的纯二进制websocket连接期间保留TestSecurityContextHolder的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an spring boot (1.5.2.RELEASE) app that is using binary websocket (i.e. NO Stomp, AMQP pure binary buffer). In my test I am able to send messages back and forth which works just great.

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

However I am experiencing the following unexplained behaviour related to TestSecurityContexHolder during the websocket calls to the application.

TestSecurityContextHolder的上下文已开始正确设置,即我的客户@WithMockCustomUser正在对其进行设置,当在测试开始时放置breankpoint时,我可以验证这一点.即

The TestSecurityContextHolder has a context that is begin set correctly i.e. my customer @WithMockCustomUser is setting it and I can validate that when putting a breankpoint in the beginning of the test. I.e.

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>, 

效果很好,我能够测试实现方法级安全性的服务器端方法,例如

That works great and I am able to test server side methods that implement method level security such as

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

我开始遇到的问题是,当我想对应用程序进行完全集成测试时,即在测试中,我仅使用特定于Java的注释创建了我自己的WebSocket连接到应用程序,即(客户端中没有spring注释)

The problem I have starting experiencing is when I want to do a full integration test of the app i.e. within the test i crate my own WebSocket connection to the app, using only java specific annotations i.e. (no spring annotaions in the 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);
        }
    }
    ….
}

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

If try calling the websocket then I first see that the "SecurityContextPersistenceFilter" is removing the current SecurityContex which is fully expected. I actually want it to get remove since I want to test authentication anyway, since authentication is part of the websocket communication and not part of the http call in my case, but what bothers me is the following. So far we had only one HTTP call (wireshark proves that) and the SecurityContextPersistenceFilter has cleared the session only once and by setting a breakpoint on the clear method i see that indeed it has been called only once. After 6 binary messaged (i.e. the SecurityContext is set in the 5 message received from the client) are being exchanged between the client and the server I do authentication with a custom token and write that token to the TestSecurityContextHolder btw SecurityContexHolder i.e.

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

            token.setAuthenticated(true);

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

我看到该令牌的hashCode在购买的ContexHolders中是相同的,这意味着这是同一对象.但是,下一次我从客户端收到ByteBuffer时,SecuriyContextHolder.getAuthentication()的结果为null.我首先了解到他与SecurityContextChannelInterceptor有关,因为我读了一篇有关websockets和spring的好文章,即

I see that the hashCode of that token is the same in bought ContexHolders which means that this is the same object. However next time I received a ByteBuffer from the client, the result of SecuriyContextHolder.getAuthentication() is null. I first though that his is related to the SecurityContextChannelInterceptor since i read a good article about websockets and spring i.e. here but this does not seems to be the case. The securityContextChannelInterceptor is not executed or called anywhere at least when putting breakpoints i see that IDE is not stopping there. Please note that I am deliberately not extending the AbstractWebSocketMessageBrokerConfigurer here since i do not need it i.e. this is plain simple binary websocket with no (STOMP AMQP etc. i.e. no known 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 

但仅在测试完成时!!!即在SecurityContext为null之后的方式,尽管之前是使用客户令牌手动设置的.看起来像过滤器(但对于websocket,即不是HTTP)的东西正在清除每个接收到的WsFrame上的securityContext.我不知道那是什么.也可能是相对的:在服务器端,当我看到堆栈跟踪时,我可以观察到正在调用StandardWebSocketHandlerAdapter的事件正在创建StandardWebSocketSession.

but only when the test finished!!! i.e. that is way after the SecurityContext is null, although manually set with customer token before. It seems that something like a filter (but for websockets i.e. not HTTP) is clearing the securityContext on each WsFrame received. I have no idea what that is. Also what might be also relative is: on the server side when i see the stack trace i can observe that StandardWebSocketHandlerAdapter is being called which is creating the StandardWebSocketSession.

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

在StandardWebSocketSession中,我看到有一个主要用户"字段.那么谁应该设置该主体,即我看不到任何设置方法,唯一的设置方法是在"AbstractStandardUpgradeStrategy"期间,即在第一次调用中,但是一旦建立会话,该怎么办?即rfc6455定义了

In the StandardWebSocketSession i see that there is a field "Principal user". Well who is supposed to set that principal i.e. i do not see any set methods there the only way to set it is is during the "AbstractStandardUpgradeStrategy" i.e. in the first call but then what to do once the session it established? i.e. the rfc6455 defined the

10.5. WebSocket客户端身份验证 该协议没有规定服务器可以采用的任何特定方式 在WebSocket握手期间对客户端进行身份验证. WebSocket 服务器可以使用任何可用的客户端身份验证机制

10.5. WebSocket Client Authentication This protocol doesn't prescribe any particular way that servers can authenticate clients during the WebSocket handshake. The WebSocket server can use any client authentication mechanism available

对我来说,这意味着我应该能够在以后的任何时候定义用户主体.

for me that means that i SHOULD be able to define the user Principal in the later stage whenever i want.

这是运行测试的方法

@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返回的Security Context为null的行为?

And in order to summarize my question is what could be causing the behaviour where Security Context returned from the bought TestSecurityContextHolder or SecurityContextHolder is null after another ByteBuffer (WsFrame) is being received from the client?.

@ 5月31日新增: 我巧合地发现,在多次运行测试时,有时contex不为空,并且测试正常,即有时con确实确实充满了我提供的令牌.我想这与Spring Security Authentication绑定到ThreadLocal的事实有关,将需要进一步挖掘.

@Added 31 May: I found by coincidence when running the test mulitple times that sometimes the contex is not null and the test OK i.e. sometimes the contex is indeed filled with the token i supplied. I guess this has something to do with the fact that the Spring Security Authentication is bound to a ThreadLocal, will need further digging.

@添加于2017年6月6日: 我可以确认知道问题出在线程中,即身份验证成功,但是当从http-nio-8081-exec-4跳转到nio-8081-exec-5之间跳转时,安全Contex会丢失,并且在这种情况下我已经将SecurityContextHolder策略设置为MODE_INHERITABLETHREADLOCAL.任何建议都将不胜感激.

@Added 6 June 2017: I can confirm know that the problem is in the threads i.e.the authentication is successful but when jumping between http-nio-8081-exec-4 to nio-8081-exec-5 the Security Contex is beeing lost and that is in the case where i have set the SecurityContextHolder Strategy to MODE_INHERITABLETHREADLOCAL. Any sugesstions are greatly appreciated.

添加2017年6月7日
如果我添加了SecurityContextPropagationChannelInterceptor,则在简单的websocket情况下不会传播安全上下文.

Added 07 June 2017
If i add the SecurityContextPropagationChannelInterceptor does not propagate the security Context in case of the simple 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等那个.

Added 12 June 2017
did the test with the Async notation i.e. the one found here. spring-security-async-principal-propagation . That is showing that the Security Context is being transferred correctly between methods that are executed in different threads within spring, but for some reason the same thing does not 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 the feeling that his has something to do with the executor but so far i do not know how to change that.

添加了2017年6月13日

Added 13 June 2017

通过打印当前线程和Security Contex,我发现第一个线程(即http-nio-8081-exec-1)确实具有按预期填充的安全上下文,即按照模式MODE_INHERITABLETHREADLOCAL,但是所有其他线程(如http- nio-8081-exec-2,http-nio-8081-exec-3都没有.现在的问题是:这是预期的吗?我在这里找到在春天的声明

I have found by printing the current threads and the Security Contex that the very first thread i.e. http-nio-8081-exec-1 does have the security context populated as expected i.e. per mode MODE_INHERITABLETHREADLOCAL, however all further threads i.e http-nio-8081-exec-2, http-nio-8081-exec-3 do not. Now the question is: Is that expected? I have found here working with threads in Spring the statement that

您不能在兄弟线程之间(例如,在线程池中)共享安全上下文.此方法仅适用于由已包含已填充SecurityContext的线程产生的子线程.

基本上可以解释这一点,但是由于在Java中无法找到线程的父级,我想问题是谁在创建线程http-nio-8081-exec-2,这是调度程序servlet的原因.还是那tomcat以某种方式神奇地决定了我现在将创建一个新线程.我之所以这么问,是因为我看到有时部分代码在同一线程中执行,或者根据运行情况以不同的方式执行.

which basically explains it, however since in Java there is no way to find out the parent of the thread , I guess the question is who is creating the Thread http-nio-8081-exec-2 , is that the dispatcher servlet or is that tomcat somehow magically deciding now i will create a new thread. I am asking that because i see that sometimes parts of the code are executed in the same thread or in different depending on the run.

添加2017年6月14日

Added 14 June 2017

由于我不想一概而论,因此我创建了一个单独的问题,该问题涉及寻找答案的问题,该问题如何在Spring Boot应用程序的情况下将安全上下文传播到由tomcat创建的所有同级线程中.在此处

Since i do not want to put all in one i have created a separated question that deals with the problem of finding the answer how to propagate the security context to all sibling threads created by the tomcat in case of a spring boot app. found here

推荐答案

我不确定是否100%理解了这个问题,但是Java调度程序servlet不太可能在不被告知的情况下创建新线程.我认为tomcat在不同的线程中处理每个请求,因此这可能就是创建线程的原因.您可以检查.祝你好运!

I'm not 100% sure I understand the problem, but it's unlikely that the Java dispatcher servlet will create a new thread without being told to. I think tomcat handles each request in a different thread, so that might be why the threads are being created. You can check this and this out. Best of luck!

这篇关于在Spring Boot测试中的纯二进制websocket连接期间保留TestSecurityContextHolder的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆