I'm building a Spring Boot chat application with:
I have a ChannelInterceptor that:
- On CONNECT, parses the JWT from the Authorization header
- Validates JWT and creates an Authentication object (with authorities)
- Calls StompHeaderAccessor.setUser(authentication)
My controller looks like:
@MessageMapping("/chat/message.send")
@PreAuthorize("hasAuthority('SCOPE_WRITE')")
public void sendMessage(@Payload MessageRequestDto dto) {
}
But whenever a STOMP SEND is received, I get: AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
because of :
private Authentication getAuthenticationOrThrow() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated())
throw new AuthenticationCredentialsNotFoundException("No authenticated user found in SecurityContext");
return authentication;
}
How do I properly wire up Spring Security so that the user authenticated at STOMP CONNECT (via JWT) is available in the SecurityContext for each @MessageMapping?(i can provide github link if needed - https://github.com/engly817chat/engly-server) it is a univ project
I implemented a custom ChannelInterceptor which, on each STOMP CONNECT, extracts the Authorization header from the STOMP frame, validates the JWT, and sets the authenticated user using accessor.setUser(authentication). I also tried (as suggested by Spring/StackOverflow answers) adding a second ChannelInterceptor that, for every inbound message, copies accessor.getUser() (if it's an Authentication) into the SecurityContextHolder. I confirmed in logs that accessor.getUser() is a valid Authentication object during message handling, with the correct authorities.
SecurityContextHolder
is not automatically populated during STOMP message handling. Spring doesn't do that for you like it does with HTTP. In your ChannelInterceptor
, for each inbound message (not just CONNECT), check if accessor.getUser()
is an Authentication
and manually set it in SecurityContextHolder
. Here is what you need inside preSend
method.
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String jwt = accessor.getFirstNativeHeader("Authorization");
if (jwt != null && jwt.startsWith("Bearer ")) {
jwt = jwt.substring(7); // remove "Bearer "
Authentication auth = jwtService.validateToken(jwt); // your custom method
accessor.setUser(auth); // set the user on the accessor
}
} else {
// For every other frame, set the auth into SecurityContext
Principal user = accessor.getUser();
if (user instanceof Authentication authentication) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
}
return message;
}