this.stompClient.send("/app/chat/send", {"Authorization" : "Bearer " + this.token}, JSON.stringify(message))
Below is all the code
Код: Выделить всё
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/// this method initiates the web socket connection, when a client wants to upgrade their
/// protocol from HTTP to WebSocket
/// . we also define the servers that can make initiate a websocket connection
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("http://localhost:49322")
.withSockJS();
}
/// we define the prefix of the placeholder in the url as 'app' in which this will be binded to the
/// MessageMapping annotation methods, similar to how requestMapping routed the endpoint to the specific method
/// the message broker is used to define the endpoint in which a user will be subscribed to
/// we set the user as the prefix
///
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/user");
registry.setApplicationDestinationPrefixes("/app");
}
}
< /code>
package com.example.Dormly.websocket;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
@org.springframework.stereotype.Controller
@RequiredArgsConstructor
public class Controller {
private final SimpMessagingTemplate simpMessagingTemplate;
///
/// The @MessageMapping is used to route all /app/placeholder destinations to their specific methods
/// we use the principal to define the user who is the sender, and via a UI action
/// we also include recipient to define who the end user is
/// we create an output message object which contains a sender and the content
/// we then send this output message to the user
/// The message object just has the recipient(to whom we send to) and the content, the sender is fetched from the principal
@MessageMapping("/chat/send")
public void sendMessage(@Payload Message message , @AuthenticationPrincipal UserDetails user){
OutputMessage outputMessage = new OutputMessage(
message.getContent(),
user.getUsername() /// user who sent the message - the recipient will see this
);
/// the server will send back something like '/user/james/queue/chat'
simpMessagingTemplate.convertAndSendToUser(message.getRecipient(),"/queue/chat", outputMessage);
}
}
< /code>
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthFilter jwtAuthFilter;
/**
* Configuration annotation tells spring that there is more than one bean that needs to be instantiated as a singleton
* SecurityFilterChain applies a set of filters to our HTTP requests.
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.
cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(http->http.
requestMatchers("/api/v1/Sign-up").permitAll()
.requestMatchers("/api/v1/login").permitAll()
.requestMatchers("/ws**").permitAll()
.anyRequest()
.authenticated()
)
/**
* ensure our session management remains stateless, as we authenticate once per request
*/
.sessionManagement(session-> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
/**
* when spring security intercepts the login request, the usernamepasswordfilter delegates
* this to the auth manager in which that uses the provider manager impl to find the auth provider.
* The auth provider is called to validate the credentials as it receives an auth object
* Also ensure the jwt filter gets called before the usernamepass filter
* as we always need to check if a JWT is present, if not the request is passed to other filters
* This way we handle requests for unauthenticated and authenticated users
* not authenticated(meaning no jwt) -> UsernamePassFilter gets used
* authenticated(has Jwt) -> jwtAuthFilter
*/
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
< /code>
import { Injectable } from '@angular/core';
import { TokenService } from '../auth/token/token.service';
import SockJS from 'sockjs-client';
import { Stomp } from '@stomp/stompjs';
import { Message } from '../models/Message';
import { MessagesComponent } from '../messages/messages.component';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WebSocketApiService {
//create a subject to communicate the responses back to the messages component
private subject = new BehaviorSubject(null)
messageSubscription$ = this.subject.asObservable()
destination:string = "/user/queue/chat"
brokerURL:string = "http://localhost:8099/ws"
stompClient:any
token!:string
constructor(private tokenService:TokenService) {
this.token = this.tokenService.token as string
}
connect(){
console.log("connecting to websocket...")
const headers = { "Authorization": "Bearer " + this.token };
console.log("WebSocket headers:", headers); // Log the headers
let ws = new SockJS(this.brokerURL)
this.stompClient = Stomp.over(ws)
this.stompClient.connect({"Authorization" : `Bearer ${this.token}`}, () =>{
console.log("WebSocket connected");
///the stompclient takes 3 parameters which includes the headers and 2 callback functions,
///the frames defined the handshake agreement of protocol switches, and once the event occurs we can now subscribe to the destination
this.stompClient.subscribe(this.destination, (message:any)=>{
//the subscribe also triggers a callback which means when a user subscribes to a destination, an event of a message could be returned
this.onMessageRecieved(message)
})
},
///error callback
(error:Error | any)=>{
this.errorCallBack(error)
}
)
}
errorCallBack(error:Error):void{
console.log(error.message)
}
disconnect(){
if(this.stompClient!==null){
this.stompClient.disconnect()
console.log("disconnected")
}
setTimeout(()=>{
this.connect()
},
5000) //reconnect after 5 seconds
}
onMessageRecieved(message:any) {
if(message){
///manual deserialization with websockets. using Json.parse() to convert the json into a javascript objecy
console.log("message recieved ", message)
this.subject.next(JSON.parse(message))
console.log("added message to the subject")
}
}
send(message:Message):void{
///when the user sends a message, our in memory message broker in spring will automatically forward it to the destination the user is subscribed to
///the user will recieve the message immediatley assuming the connection is still live - if not we persist the chat to the db
///all messages are sent with the /app prefix and users are able to send messages
///the message object includes the recieptent and the message itself.
///websocket does not serialize the object into a json like HTTP does, hence we do it manually
this.stompClient.send("/app/chat/send", {"Authorization" : "Bearer " + this.token}, JSON.stringify(message))
}
}
[Log] calling service class to connect to websocket (main.js, line 2372)
[Log] connecting to websocket... (main.js, line 2275)
[Warning] Stomp.over did not receive a factory, auto reconnect will not work. Пожалуйста, см. Https://stomp-js.github.io/api-
docs/latest/classes/stomp.html#over (@stomp_stompjs.js, line 1534)
[log] Открытие веб-сокета ... (@stomp_stompjs.js, строка 1286)
[velly] [v venzuz] (Клиент, строка 859)
[ошибка] Не удалось загрузить ресурс: сервер ответил статусом 403 () (информация, строка 0)
[log] соединение закрыто для http: // localhost: 8099/ws (@stomp_stompjs.js, line 1286)>
Подробнее здесь: https://stackoverflow.com/questions/794 ... pring-boot