admin管理员组文章数量:1277314
I have spring security with JWT implemented in my application and was trying to set up the WebSocket connection. Initially I was sending the token in the headers of the WebSocket connection with the stomp client, however I realized that spring security seems to handle authentication differently compared to HTTP requests.I then tried to test to see if I was able to get a connection up and running as I set permit All on the WebSocket connection endpoint in the filter chain, having done this I still get a 403 forbidden error. Currently if my spring security filter chain permits the handshake connection the connection should be successful considering I already set up spring security CORS configuration to accept my client as the origin. If I were to ensure WebSocket connections are permitted to users but the sending of messages requires authentication, would it mean I need to implement a custom message interceptor to validate my token for each message or can i just extend the AbstractSecurity WebSocketMessageBroker Configurer and ensure all endpoints with /app** are authenticated.
How would I authenticate subsequent requests under the STOMP protocol? Considering the Initial WebSocket connection is sent with under a HTTP protocol, even if I was to include the Authorization header then shouldn't it automatically be used by my JWT filter?
from the little documentation out there, what I've gathered is that if you include the token during the WebSocket handshake then because its under a HTTP protocol, your server will be able to validate the token. This would then set up the principal such that you can now set up subsequent messages to be authenticated by implementing the configureInbound method. However if I permit the handshake to not require authentication then I would need to implement a custom message interceptor that extracts the token from the send function on the the client side. something like
this.stompClient.send("/app/chat/send", {"Authorization" : "Bearer " + this.token}, JSON.stringify(message))
Below is all the code
import .springframework.context.annotation.Configuration;
import .springframework.messaging.simp.config.MessageBrokerRegistry;
import .springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import .springframework.web.socket.config.annotation.StompEndpointRegistry;
import .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");
}
}
package com.example.Dormly.websocket;
import lombok.RequiredArgsConstructor;
import .springframework.messaging.handler.annotation.MessageMapping;
import .springframework.messaging.handler.annotation.Payload;
import .springframework.messaging.simp.SimpMessagingTemplate;
import .springframework.security.core.annotation.AuthenticationPrincipal;
import .springframework.security.core.userdetails.UserDetails;
@.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);
}
}
@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();
}
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/messagesponent';
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))
}
}
This is what shows up in the console in order
[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. Please see docs/latest/classes/Stomp.html#over (@stomp_stompjs.js, line 1534)
[Log] Opening Web Socket... (@stomp_stompjs.js, line 1286) [Debug] [vite] connected. (client, line 859)
[Error] Failed to load resource: the server responded with a status of 403 () (info, line 0)
[Log] Connection closed to http://localhost:8099/ws (@stomp_stompjs.js, line 1286)
本文标签: java403 forbidden using websockets with spring bootStack Overflow
版权声明:本文标题:java - 403 forbidden using websockets with spring boot - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741237982a2363402.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论