[Stomp] Spring Boot 구현
설정
- build.gradle 파일
implementation 'org.springframework.boot:spring-boot-starter-websocket'
- WebSocketConfiguration
@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/{endpoint}")
.setAllowedOriginPatterns("로컬경로", "서버경로")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/pub");
sub** , “/topic”가 api에 prefix로 붙은 경우, **messageBroker**가 해당 경로를 가로챔 */
config.enableSimpleBroker("/sub");
config.setPreservePublishOrder(true);
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(stompHandler);
}
}
Controller 구현
@Controller
@RequiredArgsConstructor
@CrossOrigin(origins = "*", allowCredentials = "true")
@Slf4j
public class StompPersonalChatController {
@MessageMapping(value = "/{endpoint}/send/{roomId}")
public void sendMessage(@DestinationVariable String personalChatId, Message messageReq)
throws IOException {
log.debug("[StompChatController - sendMessage]: room id = {}", roomId);
Message message = messageService.registMessage(roomId, messageReq);
template.convertAndSend("/sub/" + roomId, message);
}

Spring Security
- jwt 인증시 websocket은 인증 방식을 다르게 설정하므로, 기존 jwt 인증 filter에서 websocket은 제외
기존 설정에서 제외
- FilterChain.java
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
if (!path.startsWith("/api") || path.matches("/api/.*/v0(/.*)?") || path.matches("/api/chat(/.*)?")) {
filterChain.doFilter(request, response);
return;
}
....
- SecurityConfig
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
...
http
....
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/*/v0/**").permitAll()
.requestMatchers("/api/*/v0").permitAll()
.requestMatchers("/api/chat/**").permitAll()
.anyRequest().authenticated()
)
....;
return http.build();
}
Interceptor로 구현
@Component
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class StompHandler implements ChannelInterceptor {
private final JWTUtil jwtUtil;
private final APIUserDetailsService apiUserDetailsService;
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Map<String, Object> payload = validateAccessToken(
Objects.requireNonNull(accessor.getFirstNativeHeader("Authorization")));
System.out.println(payload);
String memberId = (String) payload.get("memberId");
UserDetails userDetails = apiUserDetailsService.loadUserByUsername(memberId);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
return message;
}