为什么 SimpUserRegistry 在 EC2 实例上无法正常工作

2024-02-05

我在用SimpUserRegistry获取在线用户数(使用getUserCount())。它在我的本地计算机上运行良好,但在只有弹性 IP 且没有负载均衡器的 AWS EC2 实例(尝试使用 Amazon Linux 和 Ubuntu)上运行不佳。

EC2 上的问题是,某些用户在连接时从未添加到注册表中,因此我得到了错误的结果。

我有会话侦听器,用于SessionConnectedEvent and SessionDisconnectEvent,我使用的地方SimpUserRegistry(自动连接)以获取用户存在。如果有关系的话我也是SimpUserRegistry是一个消息传递控制器。

以下是 websocket 消息代理配置:

@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WebSocketMessageBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @NonNull
    private SecurityChannelInterceptor securityChannelInterceptor;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(1);
        threadPoolTaskScheduler.setThreadGroupName("cb-heartbeat-");
        threadPoolTaskScheduler.initialize();

        config.enableSimpleBroker("/queue/", "/topic/")
                .setTaskScheduler(threadPoolTaskScheduler)
                .setHeartbeatValue(new long[] {1000, 1000});

        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(securityChannelInterceptor);
    }
}

下面是上面配置类中使用的通道拦截器:

@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SecurityChannelInterceptor extends ChannelInterceptorAdapter {

    @NonNull
    private SecurityService securityService;


    @Value("${app.auth.token.header}")
    private String authTokenHeader;



    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        StompCommand command = accessor.getCommand();

        if (StompCommand.CONNECT.equals(command)) {
            List<String> authTokenList = accessor.getNativeHeader(authTokenHeader);
            if (authTokenList == null || authTokenList.isEmpty()) {
                throw new AuthenticationFailureException("STOMP " + command + " missing " + this.authTokenHeader + " header!");
            }
            String accessToken = authTokenList.get(0);
            AppAuth authentication = securityService.authenticate(accessToken);
            log.info("STOMP {} authenticated. Authentication Token = {}", command, authentication);
            accessor.setUser(authentication);
            SecurityContextHolder.getContext().setAuthentication(authentication);

            Principal principal = accessor.getUser();
            if (principal == null) {
                throw new RuntimeException("StompHeaderAccessor did not set the authenticated User for " + authentication);
            }
        }

        return message;
    }

}

我还有以下计划任务,它每两秒打印一次用户名:

@Component
@Slf4j
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserRegistryLoggingTask {

    private SimpUserRegistry simpUserRegistry;

    @Scheduled(fixedRate = 2000)
    public void logUsersInUserRegistry() {
        Set<String> userNames = simpUserRegistry.getUsers().stream().map(u -> u.getName()).collect(Collectors.toSet());
        log.info("UserRegistry has {} users with IDs {}", userNames.size(), userNames);
    }
}

有些用户名即使连接后也不会显示。

实施SecurityService class -

@Service
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class SecurityService {

    private UserRepository userRepository;
    private UserCredentialsRepository userCredentialsRepository;
    private JwtHelper jwtHelper;

    public User getUser() {
        AppAuth auth = (AppAuth) SecurityContextHolder.getContext().getAuthentication();
        User user = (User) auth.getUser();
        return user;
    }

    public AppAuth authenticate(String accessToken) {
        String username = jwtHelper.tryExtractSubject(accessToken);
        if (username == null) {
            throw new AuthenticationFailureException("Invalid access token!");
        }

        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new AuthenticationFailureException("Invalid access token!");
        }

        AppAuth authentication = new AppAuth(user);
        return authentication;
    }
}

Update

以下是 SockJS 在浏览器上登录的示例 -

来自服务器的正确响应user-name header:

>>> CONNECT
AccessToken:eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJkb2cifQ.Wf8AO77LluHEfEv61TIvugEXxOqIXKjsJBO8QMQh-rF7tzf56lBkdpOruqc7UPf_Pmj6-dnHZ5raq2MnMpeG8Q
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
version:1.1
heart-beat:1000,1000
user-name:5a590e411b96f841cc00027f

来自服务器的错误响应没有user-name header:

>>> CONNECT
AccessToken:eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJtb3VzZSJ9.wqX5X_CSdHD8_7PZPiSzftGCuPz1ClQU0-F9RHCqOIIkMLzI4rt31_EAaykc8VojK2KGS6DcycWfAdMr2edzYg
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
version:1.1
heart-beat:1000,1000

我还验证了SecurityChannelInterceptor正在对所有用户进行身份验证,即使user-name不在CONNECTED回复。

Update

我在heroku上部署了该应用程序。这个问题也在那里发生。

Update

当问题发生时,user in SessionConnectEvent是由SecurityChannelInterceptor but user in SessionConnectedEvent is null.

Update

AppAuth class -

public class AppAuth implements Authentication {

    private final User user;
    private final Collection<GrantedAuthority> authorities;


    public AppAuth(User user) {
        this.user = user;
        this.authorities = Collections.singleton((GrantedAuthority) () -> "USER");
    }

    public User getUser() {
        return this.user;
    }

    @Override
    public String getName() {
        return user.getId();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getDetails() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return new Principal() {
            @Override
            public String getName() {
                return user.getId();
            }
        };
    }

    @Override
    public boolean isAuthenticated() {
        return true;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

    }
}

经过一些调试后,我能够通过在中添加一些记录器语句来跟踪问题StompSubProtocolHandler。 找到原因后,结论是通道拦截器不是验证用户身份的正确位置。至少对于我的用例来说是这样。

以下是一些代码片段StompSubProtocolHandler https://github.com/spring-projects/spring-framework/blob/v4.3.14.RELEASE/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java -

The handleMessageFromClient方法将用户添加到stompAuthentications地图并发布SessionConnectEvent event -

public void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> webSocketMessage, MessageChannel outputChannel) {
    //...
    SimpAttributesContextHolder.setAttributesFromMessage(message);
    boolean sent = outputChannel.send(message);

    if (sent) {
        if (isConnect) {
            Principal user = headerAccessor.getUser();
            if (user != null && user != session.getPrincipal()) {
                this.stompAuthentications.put(session.getId(), user);
            }
        }
        if (this.eventPublisher != null) {
            if (isConnect) {
                publishEvent(new SessionConnectEvent(this, message, getUser(session)));
            }
    //...

And the handleMessageToClient从中检索用户stompAuthentications地图并发布SessionConnectedEvent -

public void handleMessageToClient(WebSocketSession session, Message<?> message) {
    //...
    SimpAttributes simpAttributes = new SimpAttributes(session.getId(), session.getAttributes());
    SimpAttributesContextHolder.setAttributes(simpAttributes);
    Principal user = getUser(session);
    publishEvent(new SessionConnectedEvent(this, (Message<byte[]>) message, user));
    //...

getUser上述方法所使用的方法 -

private Principal getUser(WebSocketSession session) {
    Principal user = this.stompAuthentications.get(session.getId());
    return user != null ? user : session.getPrincipal();
}

现在,当handleMessageToClient代码片段在之前执行handleMessageFromClient片段。在这种情况下,用户永远不会添加到DefaultSimpUserRegistry,因为它只检查SessionConnectedEvent.

下面是事件监听器片段DefaultSimpUserRegistry https://github.com/spring-projects/spring-framework/blob/v4.3.14.RELEASE/spring-websocket/src/main/java/org/springframework/web/socket/messaging/DefaultSimpUserRegistry.java -

public void onApplicationEvent(ApplicationEvent event) {
    //...
    else if (event instanceof SessionConnectedEvent) {
        Principal user = subProtocolEvent.getUser();
        if (user == null) {
            return;
        }
    //...

Solution

解决方案是延长DefaultHandshakeHandler https://github.com/spring-projects/spring-framework/blob/v4.3.14.RELEASE/spring-websocket/src/main/java/org/springframework/web/socket/server/support/DefaultHandshakeHandler.java并覆盖determineUser方法,该方法基于这个答案 https://stackoverflow.com/a/33537534/738746。但是,由于我使用的是 SockJS,这需要客户端发送 auth-token 作为查询参数。并讨论了查询参数要求的原因here https://github.com/sockjs/sockjs-client/issues/196.

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

为什么 SimpUserRegistry 在 EC2 实例上无法正常工作 的相关文章

  • 未找到 MessageSource 的 ResourceBundle [消息]:找不到基本名称消息的包

    在 applicationContext xml 中 我定义了 MessageSource 如下所示
  • 如何在spring mvc中从控制器名称+操作名称获取映射的URL?

    是否有现有的解决方案可以从 Spring MVC3 中的 控制器名称 操作名称 获取映射的 URL 例如 asp net mvc 或 Rails 中的 UrlHelper 我觉得非常有用 thx 也许 你想要这样的东西 in your Co
  • WebSocket 和 Origin 标头字段

    以下引用自 RFC6455 WebSocket 协议 不打算处理来自任何网页的输入但 仅对于某些站点应验证 Origin 场是原点 他们期望 如果服务器不接受指示的来源 那么它应该用回复来响应 WebSocket 握手 包含 HTTP 40
  • webhook 和 websocket 之间的区别?

    我一直想进行实时聊天 几年前我用 PHP Ajax Mysql 完成了这个任务 并破坏了我的服务器 然后我尝试使用 Flash 文本文件 我放弃了 10年没有尝试过 但最近我听说了 webhooks 和 websockets 它们似乎都是做
  • Grails 2.3.0 自动重新加载不起作用

    我最近将我们的项目升级到 grails 2 3 0 一切工作正常 除了每当我更改代码时自动重新加载都无法工作的问题 这包括所有项目工件 控制器 域 服务 gsps css 和 javascript 文件 我的旧版本 grails 可以正常工
  • Spring安全“记住我”cookie在第一个请求中不可用

    我无法在登录请求后检索 Spring 记住我 cookie 但它在对受保护页面的下一个请求中工作正常 谁能告诉我怎样才能立即得到它 我在登录请求中设置了记住我的 cookie 但在 Spring 重定向回原始 受保护的 url 后无法检索它
  • eclipse中导入项目文件夹图标

    我在 Eclipse 工作区中新导入的 Maven 项目有J and M项目文件夹顶部的图标 项目和包资源管理器 而其他导入的 Maven 项目只有一个J icon 有人可以解释其中的区别吗 该项目有J装饰器被称为 Java 项目和具有M装
  • 如何在 JSP 中导入类?

    我是一个完全的JSP初学者 我正在尝试使用java util List在 JSP 页面中 我需要做什么才能使用除以下类之外的类java lang 使用以下导入语句进行导入java util List 顺便说一句 要导入多个类 请使用以下格式
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 如何将 android.net.Uri 转换为 java.net.URL? [复制]

    这个问题在这里已经有答案了 有没有办法从Uri to URL 我正在使用的库需要这个 它only接受一个URL但我需要在我的设备上使用图像 如果该方案的Uri is http or https new URL uri toString 应该
  • 具有共享依赖项的多模块项目的 Gradle 配置

    使用 gradle 制作第一个项目 所以我研究了 spring gradle hibernate 项目如何组织 gradle 文件 并开始制作自己的项目 但是 找不到错误 为什么我的配置不起作用 子项目无法解决依赖关系 所以项目树 Root
  • 无法捕获 Spring Batch 的 ItemWriter 中的异常

    我正在编写一个 Spring Batch 流程来将数据集从一个系统迁移到另一个系统 在这种情况下 这就像使用RowMapper实现在传递给查询之前从查询构建对象ItemWriter The ItemWriter称为save我的 DAO 上的
  • 对象锁定私有类成员 - 最佳实践? (爪哇)

    I asked 类似的问题 https stackoverflow com questions 10548066 multiple object locks in java前几天 但对回复不满意 主要是因为我提供的代码存在一些人们关注的问题
  • 解决错误javax.mail.AuthenticationFailedException

    我不熟悉java中发送邮件的这个功能 我在发送电子邮件重置密码时遇到错误 希望你能给我一个解决方案 下面是我的代码 public synchronized static boolean sendMailAdvance String emai
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • Android - 9 补丁

    我正在尝试使用 9 块图片创建一个新的微调器背景 我尝试了很多方法来获得完美的图像 但都失败了 s Here is my 9 patch 当我用Draw 9 patch模拟时 内容看起来不错 但是带有箭头的部分没有显示 或者当它显示时 这部
  • Nginx url 限制 502 网关

    我有一个问题 但我接受绕过此功能的其他建议 基本上 我在 get 请求中向我的服务器发送大约 3000 个字符的大行文本 然后服务器将其作为 url 中的参数发送到谷歌翻译 问题 当 url gt 1900 个字符时 Nginx 会抛出 5
  • Java &= 运算符应用 & 或 && 吗?

    Assuming boolean a false 我想知道是否这样做 a b 相当于 a a b logical AND a is false hence b is not evaluated 或者另一方面 这意味着 a a b Bitwi
  • JAXB - 列表<可序列化>?

    我使用 xjc 制作了一些课程 public class MyType XmlElementRefs XmlElementRef name MyInnerType type JAXBElement class required false
  • 启动Java项目时发生类冲突:ClassMetadataReadingVisitor将接口org.springframework.asm.ClassVisitor作为超类

    我正在使用最新的Spring框架版本 3 2 2 RELEASE 开发一个Java Web项目 但是现在项目启动时遇到了问题 详细错误是 java lang IncompleteClassChangeError 类 org springfr

随机推荐

  • C# 和 SQL Server 2008 CLR 序列化问题

    我正在尝试创建一个SqlUserDefinedAggregate用 C 附加到我的 SQL Server 2008 实例 我正在使用 NET 3 5 基本上 我想计算看到字符串值的次数 由于用途 它确实需要是一个聚合函数 该函数的代码在逻辑
  • 数据框和子图中的日期错误

    我正在尝试在 csv 文件中绘制数据 目前 如果我要转换它 我的日期也不会在图中正确显示 如何更改它以显示 Y m d 定义的正确数据格式 第二个问题是 我目前正在一个图中绘制所有数据 但希望为每个 Valuegroup 绘制一个子图 我的
  • 如何在 Perl 中删除与特定模式匹配的行?

    我想做类似的事情sed在 Perl 中 即能够删除与特定模式匹配的行 鉴于此输入 abcd edfd abcd derder abcd erre 我想删除包含的行bc 我怎样才能做到这一点 我必须在 Windows 上使用双引号 perl
  • Python - 显示 csv 文件中具有重复值的行

    我有一个包含几列的 csv 文件 其中一列填充了随机数 我想在那里找到重复的值 如果有 奇怪的情况 但这毕竟是我想要检查的 我想显示 存储存储这些值的完整行 为了说清楚 我有这样的事情 第一 无论什么 230 无论什么 等等第二个 任意 1
  • HttpMediaTypeNotAcceptableException

    我的 jQuery 函数有问题 我想要实现的是在列表框中填充数据 JavaScript 函数 function load getJSON findAdminGroupsURL ajax true function data var html
  • FPS 极低,我应该使用什么分析应用程序来查找性能问题?

    我正在创建 XNA 游戏 但得到了意想不到的结果 FPS 极低 大约 2 12 fps 我应该使用什么程序来测试性能并找出导致性能下降的原因 你有没有尝试过使用SlimTune http code google com p slimtune
  • Android:在RelativeLayout的onTouchEvent中看不到ACTION_MOVE/UP

    我注册了一个相对布局的监听器 见下文 我想添加一些自定义事件处理 mOnTouchListener new OnTouchListener Override public boolean onTouch View view MotionEv
  • 如何删除在composer中保存的用户名和密码(laravel 4)

    我已经使用 Composer 更新了我的 Laravel 供应商 我购买了一个位于 github 私人仓库中的软件包 在下载作曲家询问用户名和密码时 我输入了错误的密码 因此出现错误 之后我再次运行composer update 但这次它只
  • React useEffect 清理函数意外调用

    我正在创建一个自定义挂钩来在表单提交时获取 api 我在 useEffect 挂钩内进行 api 调用 并且我有一个减速器来处理挂钩的状态 其中一个州是trigger首先设置为 false 来控制 useEffect 是否执行任何操作 重点
  • Spyder 不运行代码中的新更改

    我正在尝试使用 Spyder 3 2 4 通过 Anaconda 更新 Python 3 6 代码 但是当我对正在处理的代码进行更改并尝试运行它时 它将运行我启动会话的代码 有谁知道这个问题有什么解决办法吗 更改您正在处理的 py 目录 右
  • 窗口最小化时,页面可见性 API 在 Chrome OS X 上不起作用

    根据 Google 的文档 他们已经实现了 Page Visibility API https developers google com chrome whitepapers pagevisibility https developers
  • Android应用程序与三星和wiko的兼容性问题

    目前我的应用程序有一个小问题 在大多数设备上一切正常 但在某些三星和 wiko 上我收到此错误 java lang NoClassDefFoundError android support v7 internal view menu Men
  • 片段示例中的 Android ListView [重复]

    这个问题在这里已经有答案了 在哪里可以找到使用示例ListView in Fragment 我知道样本中有一个例子 但不幸的是我找不到它 也许你可以建议一些东西 你的 Fragment 可以子类化列表片段 http developer an
  • Ubuntu graphviz 'sfdp' 不工作

    我正在尝试做一些Networkx Graphviz graphs 运行后 pos nx graphviz layout G prog sfdp 发生错误 说 Error remove overlap Graphviz not built w
  • Flutter桌面支持自定义文件扩展名

    我在尝试着支持自定义文件扩展名和我的颤振桌面应用程序 我没有找到任何相关内容 因为我来自移动开发 所以我不知道如何做到这一点MacOS 和 Windows Linux 一旦达到稳定 你能帮助我吗 这就是我要的 您单击目录中的 abc 文件F
  • Mercurial:如何才能只看到合并引入的更改?

    我正在努力养成进行代码审查的习惯 但是合并使这个过程变得困难 因为我不知道如何要求 Mercurial 仅显示合并引入的更改 这些更改在其父级中都不存在 或者 稍微正式一点 感谢 Steve Losh 显示合并中在其父项中不存在的每个块 并
  • 如何使用 yocto 构建简单的 qt 应用程序?

    I have yocto开发环境设置 我可以在其中进行 bitbake 并在目标中运行一个简单的 C 应用程序 现在我想尝试使用简单的 Qt 应用程序 当我执行时bitbake layers 显示层它在列表中显示meta qt5 meta
  • 使用 pandas 循环数据帧的最有效方法是什么?

    我想按顺序对数据帧中的财务数据执行我自己的复杂操作 例如 我使用以下 MSFT CSV 文件 取自雅虎财经 http finance yahoo com q hp s MSFT Date Open High Low Close Volume
  • 如何将字符编码大于127的字符串正确转换为字节数组?

    我正在从另一个系统检索使用代码页 437 编码的 ASCII 字符串 我需要将其转换为 Unicode 以便它们可以与其他 Unicode 字符串混合 这就是我正在处理的内容 var asciiString u0094 94 corresp
  • 为什么 SimpUserRegistry 在 EC2 实例上无法正常工作

    我在用SimpUserRegistry获取在线用户数 使用getUserCount 它在我的本地计算机上运行良好 但在只有弹性 IP 且没有负载均衡器的 AWS EC2 实例 尝试使用 Amazon Linux 和 Ubuntu 上运行不佳