entity에 생성일과 생성한 사용자 등을 관리하려는 중 WebSocket 통신으로 이루어진 채팅 메세지는 생성한 사용자 auditing이 안되는 현상
을 발견하게 되는데...
http 통신으로 이루어진 Chat Room 등록은 user auditing이 잘 되는데
websocket으로 이루어진 Chat Message는 user auditing이 되지 않는다..
mongoDB를 사용중이기에 @EnablieMongoAuditing이 걸려있었고 사용자 정보는 아래와 같이 SecurityContext에서 가지고 오고 있었다.
public class SpringSecurityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(CustomUserDetails.class::cast)
.map(CustomUserDetails::getUserId)
;
}
}
확인해보니 SecurityContextHolder.getContext()가 null 이어서 못 가지고 온 것이었다.
WebSocket CONNECT 시에만 JWT를 통해서 인증하고 이후에는 검증하지 않았다.
public class WebSocketInterceptor implements ChannelInterceptor {
private final JwtProvider jwtProvider;
@SneakyThrows
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor.getCommand() == StompCommand.CONNECT) {
String authToken = accessor.getFirstNativeHeader("Authorization");
if (authToken == null || !jwtProvider.validateJwt(authToken)) {
throw new AuthException("Authentication failed!!");
}
}
return message;
}
}
@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebSocketInterceptor webSocketInterceptor;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/sub");
config.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/chat").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketInterceptor);
}
}
먼저 build.gradle에 해당 dependency를 추가해준다
implementation("org.springframework.security:spring-security-messaging")
AbstractSecurityWebSocketMessageBrokerConfigurer
를 상속받은 SecurityWebSocketConfig
를 작성한다.
@Configuration
public class SecurityWebSocketConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry message) {
message
.nullDestMatcher().permitAll()
.simpDestMatchers("/pub/**").authenticated()
.simpSubscribeDestMatchers("/sub/**").authenticated()
.anyMessage().denyAll()
;
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
public class WebSocketInterceptor implements ChannelInterceptor {
private final JwtProvider jwtProvider;
@SneakyThrows
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor.getCommand() == StompCommand.CONNECT) {
String authToken = accessor.getFirstNativeHeader("Authorization");
if (authToken == null || !jwtProvider.validateJwt(authToken)) {
throw new AuthException("Authentication failed!!");
}
// UsernamePasswordAuthenticationToken 발급
UsernamePasswordAuthenticationToken authentication = jwtProvider.getAuthentication(authToken);
// accessor에 등록
accessor.setUser(authentication);
}
return message;
}
}
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebSocketInterceptor webSocketInterceptor;
...
}
👍👍