在 Spring Boot 测试中的纯二进制 websocket 连接期间保留 TestSecurityContextHolder

2024-04-18

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

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

TestSecurityContextHolder 有一个开始设置正确的上下文,即我的客户 @WithMockCustomUser 正在设置它,我可以在测试开始时放置断点时验证它。 IE。

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser>, 

这非常有效,我能够测试实现方法级安全性的服务器端方法,例如

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

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

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 仅清除了一次会话,通过在清除方法上设置断点,我发现它确实只被调用了一次。在客户端和服务器之间交换 6 个二进制消息(即 SecurityContext 在从客户端收到的 5 个消息中设置)之后,我使用自定义令牌进行身份验证,并将该令牌写入 TestSecurityContextHolder btw 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 有关,因为我读了一篇关于 websockets 和 spring 的好文章,即here https://robertleggett.wordpress.com/2015/05/27/websockets-with-spring-spring-security/但事实似乎并非如此。 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 为 null 之后的情况就是这样。似乎过滤器之类的东西(但对于 websockets,即不是 HTTP)正在清除收到的每个 WsFrame 上的 securityContext。我不知道那是什么。另外,可能相对的是:在服务器端,当我看到堆栈跟踪时,我可以观察到正在调用 StandardWebSocketHandlerAdapter 来创建 StandardWebSocketSession。

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

在 StandardWebSocketSession 中,我看到有一个字段“主要用户”。那么谁应该设置该主体,即我没有看到任何设置方法,设置它的唯一方法是在“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 返回的 Security Context 为 null 的行为可能是什么原因导致的?

@5月31日添加: 我偶然发现,在多次运行测试时,有时上下文不为空,并且测试正常,即有时上下文确实填充了我提供的令牌。我想这与 Spring Security 身份验证绑定到 ThreadLocal 有关,需要进一步挖掘。

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

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

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

添加于 2017 年 6 月 12 日
使用异步符号(即此处找到的符号)进行了测试。spring-security-async-principal-传播 http://www.baeldung.com/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 等我感觉他与执行者有关,但到目前为止我不知道如何改变那。

添加于 2017 年 6 月 13 日

我通过打印当前线程和安全上下文发现,第一个线程(即 http-nio-8081-exec-1)确实按预期填充了安全上下文,即每个模式 MODE_INHERITABLETHREADLOCAL,但是所有其他线程(即 http-nio-8081) -exec-2、http-nio-8081-exec-3 没有。现在的问题是:这是预期的吗?我在这里找到了在 Spring 中使用线程 http://www.ogrigas.eu/spring/2010/04/inherit-spring-security-context-in-child-threads的声明

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

这基本上解释了它,但是由于在Java中没有办法找到线程的父级,我想问题是谁在创建线程http-nio-8081-exec-2,是调度程序servlet还是那个tomcat不知何故神奇地决定现在我将创建一个新线程。我这么问是因为我发现有时部分代码是在同一个线程中执行的,或者根据运行情况在不同的线程中执行。

添加于 2017 年 6 月 14 日

由于我不想将所有内容都放在一个中,因此我创建了一个单独的问题,该问题处理寻找答案的问题,即在 Spring Boot 应用程序的情况下如何将安全上下文传播到 tomcat 创建的所有同级线程。成立here https://stackoverflow.com/questions/44537723/how-to-run-delegatingsecuritycontextrunnable-every-time-when-tomcat-creates-new


我不能 100% 确定我理解这个问题,但 Java 调度程序 servlet 不太可能在没有被告知的情况下创建一个新线程。我认为 tomcat 在不同的线程中处理每个请求,因此这可能就是创建线程的原因。你可以检查this https://stackoverflow.com/questions/3401992/does-tomcat-create-a-thread-per-user and this https://stackoverflow.com/questions/28776332/how-does-tomcat-create-threads-internally出去。祝你好运!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Spring Boot 测试中的纯二进制 websocket 连接期间保留 TestSecurityContextHolder 的相关文章

随机推荐

  • 如何为 UILabel 指定日语编码?

    当我尝试在 iOS 上的 UILabel 中显示日语字符串时 它会使用中文编码而不是日语显示 这两个编码是nearly除少数特殊情况外 完全相同 例如 以下是字符 直 Unicode U 76F4 在中文 上 与日文 下 中的呈现方式 se
  • 在 Python 中设置系统日期(在 Windows 上)

    似乎有许多软件包用于获取 格式化当前日期 或查找从现在开始的 n 个时间间隔的日期 但我一定忽略了在 Python 中设置日期的简单方法 如 Windows 的 date exe 的存在 这样的功能肯定存在吗 我无法在 Google Pyt
  • 将字符串传递给 settimeout 是不好的做法吗?如果是,为什么? [复制]

    这个问题在这里已经有答案了 可能的重复 是否有充分的理由将字符串传递给 setTimeout https stackoverflow com questions 6081560 is there ever a good reason to
  • 无法获取动态导入的模块:

    我有一些延迟导入的 React 组件App tsx App tsx用于Index tsx它被渲染并附加到的地方body const IndexPage lazy gt import features IndexPage const Tags
  • 在 Playstore 中发布现成的 Kivy 应用程序之前的流程?

    我已经做了buildozer android debug并拥有 apk文件已安装并且工作正常 至少针对一个 两个设备进行了测试 这也是半默认的buildozer spec 例如 域名仍然是org test但随着应用程序的名称进行了调整 有了
  • 如何在可拖动和可放置之间划清界限?

    我正在使用优秀的 JQuery UI 进行 映射 以便用户可以 映射 来自一个计划的人员与来自另一计划的人员 使用这个简单的 JQuery document ready function div draggable draggable re
  • 如何在 iOS 上检测设备的方向?

    我有一个关于如何在 iOS 上检测设备方向的问题 我不需要接收更改通知 只需接收当前方向本身 这似乎是一个相当简单的问题 但我一直无法理解它 以下是我到目前为止所做的 UIDevice myDevice UIDevice currentDe
  • 插件 AppDomains 解决方法

    在处理自己的子目录中的插件程序集时 存在一个众所周知的问题 一旦这些程序集尝试从其子目录加载各自的依赖项 它们将无法加载 解决方案是在 AppDomains 中加载插件PrivateBinPath设置在他们的AppDomainSetup初始
  • 使用当前活动的 Google Drive 集成

    是否可以在不创建自己的 Activity 的情况下与 Google Drive 集成 而只是使用应用程序的当前 Activity 而不用 Google Drive 相关代码污染它 我有一个后台 服务 不是 Android 服务 只是一个与
  • 非 www 到 www htaccess 重定向

    我有一个 net 网址 它与前面的 www 完美配合 但是当网址只是http example net http example net它打开了一个正在建设的页面 我尝试了各种 htaccess 文件 但没有任何效果 帮助 我不是网络开发人员
  • CSS3 在动画元素上旋转导致不调用单击事件

    好吧 这个给我带来了很多问题 使用css3时 webkit transform具有任何类型 3d 旋转的样式 例如rotateY 30deg 给这个旋转的对象绑定点击事件是极其不可靠的 请参阅下面的示例代码或查看这把小提琴 http jsf
  • 序列化迁移执行顺序

    我似乎无法在任何地方找到这个问题的答案 我了解 Sequelize 迁移和播种器的工作原理 但我没有找到任何地方说明它们是否按某种特定顺序执行 因此 如果我从一个数据库开始 进行一系列迁移 然后决定从原始起点初始化一个全新的数据库 它将以完
  • 如何在Android应用程序中实现应用内计费?

    看来在Android应用程序中实现应用内计费是相当复杂的 我怎么能这样做呢 SDK 中的示例应用程序只有一个 Activity 这对于像我这样具有多个 Activity 的应用程序来说过于简化了 好吧 我会尝试解释一下我的经历 我不认为自己
  • Mac zip 压缩没有 __MACOSX 文件夹?

    当我在 Mac OSX 中使用内置 zip 压缩器压缩文件时 会导致在提取的 zip 中创建一个名为 MACOSX 的额外文件夹 我可以调整设置以防止创建此文件夹吗 或者我是否需要购买第三方压缩工具 UPDATE 我刚刚找到一个适用于 OS
  • 使用 simplexml_load_file 从 tumblr 中提取 - 每次都会超时

    我的网站加载时间约为 45 秒 这是因为我从 tumblr 中提取了一些 XML 但我无法确定这是我的服务器的错误 tumblr 的错误还是其他因素 我可以让这个脚本在 5 秒后超时并回显 tumblr 已关闭 吗 而不是在近一分钟后超时
  • 等待CSS中的背景图像完全加载[重复]

    这个问题在这里已经有答案了 我有一个网络应用程序 它使用外部 CSS 中加载的外部背景图像 现在 可以在图像完全渲染之前使用该应用程序 从而产生奇怪的视觉效果 如何停止脚本执行直到图像完全加载 它可以使用普通的 JavaScript 或 j
  • 如何复制hashset和hashmap,Java是否使用指针?

    我有两个问题 First 我有一个返回 HashMap 的函数 为了读取返回值 我这样写 HashMap
  • Gradle 上传失败但仍部署到 Nexus

    您好 我的 gradle 构建失败了uploadArchives任务 但是当我检查Nexus时 WAR和POM仍然成功部署到Nexus 谁能帮助我了解发生了什么事 26 Jul 2016 18 46 28 compileGroovy UP
  • 用于从模式生成 URL 的 Java 库

    我想知道是否有任何免费的 Java 库可以自动执行以下过程 1 提供遵循特定模式的 URL 例如 http www asite com path to something thischange alsothischange andthisc
  • 在 Spring Boot 测试中的纯二进制 websocket 连接期间保留 TestSecurityContextHolder

    我有一个使用二进制 websocket 的 spring boot 1 5 2 RELEASE 应用程序 即没有 Stomp AMQP 纯二进制缓冲区 在我的测试中 我能够来回发送消息 效果非常好 但是 在对应用程序进行 websocket