Я успешно настроил websocket в своем проекте, настроив его свойства безопасности, он работает нормально, как и ожидалось. Однако у меня есть несколько очень интересных случаев, когда в зависимости от типа пользователя я должен устанавливать перехватчики для соответствующих конечных точек, в которых я устанавливаю свои атрибуты заголовка, такие как (например) ID.
- Я не хочу запрещать неизвестным пользователям подключаться к некоторым конечным точкам. Что я имею в виду, если пользователь не авторизован, доступ к некоторым конечным точкам невозможен, но не ко всем.
- Я хочу проверить, существуют ли заголовки до рукопожатия. Я использовал EventListeners, но он настроен для всех конечных точек, но я хочу указать конкретные c конечные точки, такие как / user / queue / smth
Вот файл конфигурации websocket, который у меня есть создано до сих пор
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("/ws").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) {
val authorization = accessor.getNativeHeader("X-Authorization")
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("chat_id") != null && headers.getNativeHeader("chat_id")!!.isNotEmpty()){
val chatId = headers.getNativeHeader("chat_id")!![0]
if (headers.sessionAttributes != null)
headers.sessionAttributes!!["chat_id"] = chatId
}
//else //TODO THROW AN EXCEPTION OR DO SMTH
// val joiningUser = ChatRoomUser(event.user!!.name)
//
// chatRoomService!!.join(joiningUser, chatRoomService!!.findById(chatRoomId))
}
@EventListener
fun handleSessionDisconnect(event: SessionDisconnectEvent) {
val headers = SimpMessageHeaderAccessor.wrap(event.message)
val chatRoomId = headers.sessionAttributes!!["chat_id"].toString()
// val leavingUser = ChatRoomUser(event.user!!.name)
//
// chatRoomService!!.leave(leavingUser, chatRoomService!!.findById(chatRoomId))
}
}
Настройка безопасности простой
@Configuration
class WebSocketSecurityConfigurer : AbstractSecurityWebSocketMessageBrokerConfigurer() {
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
messages.anyMessage().authenticated()
}
override fun sameOriginDisabled(): Boolean {
return true
}
}
Перехватчик, который у меня до сих пор. Я знаю, что использовал перехватчики и прослушиватели событий для установки заголовков, но, насколько я понимаю, с помощью обработчиков событий вы можете устанавливать заголовки для всех соединений, верно? но я не хочу этого Я хочу установить для конкретных c конечных точек. однако я не уверен, что перехватчики - единственный способ добиться этого.
class CustomHttpSessionHandshakeInterceptor : HandshakeInterceptor {
@Throws(Exception::class)
override fun beforeHandshake(request: ServerHttpRequest, response: ServerHttpResponse, wsHandler: WebSocketHandler, attributes: MutableMap<String, Any>): Boolean {
if (request is ServletServerHttpRequest) {
attributes["custom-header"] = "foo"
}
return true
}
override fun afterHandshake(request: ServerHttpRequest, response: ServerHttpResponse, wsHandler: WebSocketHandler, @Nullable exception: java.lang.Exception?) {
}
}
Давайте подведем итоги, что необходимо сделать здесь. Первые пользователи с авторизацией могут получить доступ к указанным c конечным точкам, предоставив токены в работающих заголовках, но неизвестные пользователи также могут получить доступ к некоторым конечным точкам, к которым им разрешено, не предоставляя токены в заголовках -> здесь это должно быть проверено с использованием перехватчиков ? Во-вторых, мне нужно проверить, чтобы некоторые дополнительные заголовки были установлены пользователями, чтобы я снова проверил их, используя перехватчики, и установил эти заголовки в собственные заголовки. Есть ли возможный и правильный способ добиться этого. Заранее спасибо!