'Spring Boot Security with JWT Authentification and STOMP Websockets. STOMP Endpoint responding 404 to React frontend

when iam adding the Spring Boot Starter Security dependency on my Projekt my STOMP Endpoint responding an 404 Code to my React frontend. I build a simple Demo Projekt with only web socket dependency. In this case everything works fine. When iam adding the security dependency without any configuration i get a 403. At this point everything is fine. When iam adding the same WebSecurityConfigurerAdapter implementation as the Main Projekt everything works fine aswell. But on my main Projekt it did not work. Everytime i get a 404 on my endpoint ws://localhost:8080/socket I tried to get this work for one Week now... I cant figure it out where i should configure the Security part for the Sockets

The goul of all this is to stream progress information of some Tasks to the frontend. If you have any other solutions to build that i would be happy. It could be that websockets are not the best way to do that.

and btw. its my first Question on Stackoverflow please dont judge me if the formatting is not the best way :-)

Iam storing the User Informations in a h2 Database.

Here my Configurations and Dependencys for the Backend

pom.xml

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
        <version>2.6.6</version>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>


    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.9.0</version>
    </dependency>

The WebSecurityConfigurerAdapter implementation

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AppUserDetailService appUserDetailService;
private final Filter jwtAuthFilter;

@Autowired
public SecurityConfig(AppUserDetailService appUserDetailService, Filter jwtAuthFilter){
    this.appUserDetailService = appUserDetailService;
    this.jwtAuthFilter = jwtAuthFilter;
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(appUserDetailService);
}


@Override
protected void configure(HttpSecurity http) throws Exception {

            .antMatchers("/auth/**","/oauth/**", "/topic/**", "/socket/**", "/app/**").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/**").permitAll().and()
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
    return super.authenticationManagerBean();
}
}

JWTAuthFilter

@Slf4j
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JWTUtilService jwtUtil;

    public JwtAuthFilter(JWTUtilService jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getAuthToken(request);
        try{
            if(token != null && !token.isBlank()){
                String username = jwtUtil.extractUsername(token);
                setSecurityContext(username);
            }
        }catch (Exception e){
            log.error("No valid Token found!", e);
        }
        filterChain.doFilter(request, response);
    }

    private void setSecurityContext(String username) {
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, "", List.of());
        SecurityContextHolder.getContext().setAuthentication(authToken);
    }

    private String getAuthToken(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if(authHeader != null){
            return authHeader.replace("Bearer", "").trim();
        }
        return null;
    }
}

AppUserDetailsService implementation

@Service
public class AppUserDetailService implements UserDetailsService {
    private final AppUserRepo appUserRepo;
    private final BCryptPasswordEncoder encoder =  new BCryptPasswordEncoder();
    private final JWTUtilService jwtUtilService;

    public AppUserDetailService(AppUserRepo appUserRepo, JWTUtilService jwtUtilService) {
        this.appUserRepo = appUserRepo;
        this.jwtUtilService = jwtUtilService;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return appUserRepo.findByUsername(username)
                .map(appUser -> User
                .withUsername(username)
                .password(appUser.getPassword())
                .authorities("user")
                .build())
                .orElseThrow(()-> new UsernameNotFoundException("Username does not exist: "+username));
    }

    public String registerUser(AppUserDTO user) {
        if(!userExisting(user)){
            user.setPassword(encoder.encode(user.getPassword()));
            appUserRepo.save(user);
            return jwtUtilService.createToken(new HashMap<>(), user.getUsername());
        }else{
            throw new UserExistsException("User is currently existing.");
        }
    }
    public boolean userExisting(AppUserDTO user){
        return appUserRepo.findByUsername(user.getUsername()).isPresent();
    }
}

WebSocketMessageBrokerConfigurer implementation

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/socket").setAllowedOriginPatterns("*");
  }
}

Here my simplified version of the implementation of the React frontend

Top Level Component

import {StompSessionProvider} from "react-stomp-hooks";
<StompSessionProvider url={"ws://localhost:8080/socket"} topics={['/topic/progress']} onConnect={()=>{console.log("Connected")}} onDisconnect={()=>{console.log("Disconnected")}} onError={(err)=>{console.log(err)}}>
    <Home/>
</StompSessionProvider>

Home Component

import {useSubscription} from "react-stomp-hooks";
export default function ZapContinousHome() {
    useSubscription("/topic/progress", (message) => setMessage(message.body));
return(
<h1>Home</h1>
)
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source