如何处理spring boot stomp websocket订阅端点来授权用户

问题描述 投票:0回答:1

我们已经安全地为我们的项目成功开发了webosocket + stomp + rabbitmq。尽管我们在解决以下情况时遇到一些问题,但它工作正常:此websocket的工作流程如下:

  • 第一个用户订阅了正常工作的websocket端点

  • 在获得用户令牌授权后,用户尝试订阅以下端点'/ user / queue /'+ chatRoomId +“ .messages”。这里chatroomId定义了哪个聊天室用户连接到,这也可以正常工作,但是在这里用户可以连接任何尚未在后端验证的chatroomid我们要解决的大问题。

    stompClient.subscribe('/ user / queue /'+ chatRoomId +'.messages',来电消息);

我的问题是,当任何用户尝试订阅此端点时,我该如何验证他们?我的意思是有什么办法可以处理每个特定的订阅

这是我们的前端代码。如果您需要完整的页面,我会上传它

 function connect() {
        socket = new SockJS('http://localhost:9600/wsss/messages');
        stompClient = Stomp.over(socket);
        // var stompClient = Stomp.client("ws://localhost:9600/ws/messages");
        // stompClient.connect({ 'chat_id' : chatRoomId,
        //     'X-Authorization' : 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0Iiwic2NvcGVzIjoiUk9MRV9BRE1JTiIsImVtYWlsIjoiYWRtaW5AZ21haWwuY29tIiwiaWF0IjoxNTc5MDgxMzg5LCJleHAiOjE1ODE2NzMzODl9.H3mnti0ZNtH6uLe-sOfrr5jzwssvGNcBiHGg-nUQ6xY' },
        //     stompSuccess, stompFailure);
        stompClient.connect({ 'chatRoomId' : chatRoomId,
            'login' : 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyODg5Iiwic2NvcGVzIjoiUk9MRV9VU0VSLFJPTEVfTU9ERVJBVE9SIiwiaWF0IjoxNTgyMDMxMDA0LCJleHAiOjE1ODQ2MjMwMDR9.NGAAed4R46FgrtgyDmrLSrmd-o3tkqbF60vOg8vAWYg' },
            stompSuccess, stompFailure);
    }

    function stompSuccess(frame) {
        enableInputMessage();
        successMessage("Your WebSocket connection was successfuly established!");
        console.log(frame);

        stompClient.subscribe('/user/queue/' + chatRoomId + '.messages', incomingMessages);
        stompClient.subscribe('/topic/notification', incomingNotificationMessage);
        // stompClient.subscribe('/app/join/notification', incomingNotificationMessage);
    }

这是我在后端使用的代码

@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
class WebSocketConfig @Autowired constructor(
        val jwtTokenUtil: TokenProvider
) : WebSocketMessageBrokerConfigurer {


    @Autowired
    @Resource(name = "userService")
    private val userDetailsService: UserDetailsService? = null

    @Autowired
    private lateinit var authenticationManager: AuthenticationManager

    @Value("\${spring.rabbitmq.username}")
    private val userName: String? = null
    @Value("\${spring.rabbitmq.password}")
    private val password: String? = null
    @Value("\${spring.rabbitmq.host}")
    private val host: String? = null
    @Value("\${spring.rabbitmq.port}")
    private val port: Int = 0
    @Value("\${endpoint}")
    private val endpoint: String? = null
    @Value("\${destination.prefix}")
    private val destinationPrefix: String? = null
    @Value("\${stomp.broker.relay}")
    private val stompBrokerRelay: String? = null

    override fun configureMessageBroker(config: MessageBrokerRegistry) {
        config.enableStompBrokerRelay("/queue/", "/topic/")
                .setRelayHost(host!!)
                .setRelayPort(port)
                .setSystemLogin(userName!!)
                .setSystemPasscode(password!!)
        config.setApplicationDestinationPrefixes(destinationPrefix!!)
    }

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/websocket").setAllowedOrigins("*").setAllowedOrigins("*")
        registry.addEndpoint("/websocket/messages").addInterceptors(customHttpSessionHandshakeInterceptor()).setAllowedOrigins("*")
        registry.addEndpoint("/wsss/messages").addInterceptors(customHttpSessionHandshakeInterceptor()).setAllowedOrigins("*").withSockJS()
    }

    @Bean
    fun customHttpSessionHandshakeInterceptor(): CustomHttpSessionHandshakeInterceptor {
        return CustomHttpSessionHandshakeInterceptor()
    }

    override fun configureClientInboundChannel(registration: ChannelRegistration) {
        registration.interceptors(object : ChannelInterceptor {
            override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
                val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
                if (StompCommand.CONNECT == accessor!!.command || StompCommand.STOMP == accessor.command) {
                    val authorization = accessor.getNativeHeader("login")
                    println("X-Authorization: {$authorization}")
                    val authToken = authorization!![0].split(" ")[1]

                    val username = jwtTokenUtil.getUsernameFromToken(authToken)

                    if (username != null) {
                        if(username.contains("@")) {
                            val userDetails = userDetailsService!!.loadUserByUsername(username)

                            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                                val authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().authentication, userDetails)
                                accessor.user = authentication
                            }
                        } else {
                            val authorities = jwtTokenUtil.getAuthoritiesFromToken(authToken)
                            val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(username, "", authorities)
                            val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
                            accessor.user = authentication
                        }
                    }


                }
                return message
            }
        })
    }
}

这里是事件处理程序

@Component
class WebSocketEvents  {


    @EventListener
    fun handleSessionConnected(event: SessionConnectEvent) {

        val headers = SimpMessageHeaderAccessor.wrap(event.message)
        if ( headers.getNativeHeader("chatRoomId") != null && headers.getNativeHeader("chatRoomId")!!.isNotEmpty()){
            val chatId = headers.getNativeHeader("chatRoomId")!![0]
            if (headers.sessionAttributes != null)
                headers.sessionAttributes!!["chatRoomId"] = chatId
        }
    }

    @EventListener
    fun handleSessionDisconnect(event: SessionDisconnectEvent) {
        val headers = SimpMessageHeaderAccessor.wrap(event.message)
        val chatRoomId = headers.sessionAttributes!!["chatRoomId"].toString()
    }
}

到目前为止,我已经尝试过:如上所示,当用户首次连接到websocket端点http://localhost:9600/wsss/messages时,它正在发送令牌和聊天室ID(标题),而我正在事件监听器组件中通过将chatroomid重置为标题属性来处理此问题。我真正需要做的是在用户订阅此特定目的时获取聊天室ID,并验证他是否属于此聊天室,如果是,请授予他权限。如果不返回错误,让他加入聊天我真的很感谢任何想法或解决方法!

spring spring-boot websocket spring-websocket spring-rabbitmq
1个回答
0
投票

我花了几天时间寻找答案,但没有找到答案,所以我自己弄清楚了。这是我针对此问题的解决方案,尽管还不完整。

我已经创建了用于处理所有连接类型的单独的拦截器类,就像在捕获订阅命令时所做的那样。所以我想到了,为什么不使用Subscribe命令来监听用户采取行动并做出正确回应。例如这样

    @Component
class WebSocketTopicHandlerInterceptor constructor() : ChannelInterceptor {


    override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
        val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
        if (StompCommand.CONNECT == accessor!!.command || StompCommand.STOMP == accessor.command) {
            val authorization = accessor.getNativeHeader("login").apply { if (isNullOrEmpty()) throw LoggedError(AuthorizationException()) }
            val authToken = authorization!![0].split(" ").apply { if (size <= 1) throw LoggedError(InvalidTokenException("Token is not valid")) }[1]

            val username = jwtTokenUtil.getUsernameFromToken(authToken)

            //DO YOUR AUTHENTICATION HERE


        }



        if (StompCommand.SUBSCRIBE == accessor.command) {
            val destination = accessor.destination
            if (destination.isNullOrBlank()) throw LoggedError(CustomBadRequestException("Subscription destionation cannot be null! U DUMB IDIOT!"))
            val chatPattern = "/user/queue/+[a-zA-Z0-9-]+.messages".toRegex()
            val notificationPattern = "/topic/notification".toRegex()

            if (chatPattern.matches(accessor.destination!!)) println("working")

            // FINDING OUT WHERE USER IS TRYING TO SUBSCRIBE ALL ROUTING LOGIC GOES HERE...
            when {
                chatPattern.matches(destination) -> {
                   //do your all logic here
                }
                notificationPattern.matches(destination) -> {
                    //do your all logic here
                }
            }

        }
        return message
    }
}

是什么让我不知道,让我知道我非常乐意对任何事物进行进一步阐述。

我在本例中所做的是,我已经弄清楚了用户要去的地方,并在那里进行了所有验证,否则用户无法订阅任何频道,这意味着它非常安全。

© www.soinside.com 2019 - 2024. All rights reserved.