프로젝트에서 Spring Security를 활용하여 헤더로 JWT Token을 전달하는 방식을 사용해 로그인이 구현되었다. 하지만, WebSocket을 도입한 웹 채팅 기능에서, 정상적으로 소켓 연결이 이루어지지 않는 문제가 발생했다.
WebSocekt은 한번의 HTTP 핸드셰이크 요청을 통해 TCP 연결을 수립한다. 이 점은 HTTP를 통한 초기 연결이 가능함을 의미하며, 따라서 Spring Security를 이용한 인증 과정 역시 용이하게 진행될 것이라고 예상했다. 그러나 JWT 토큰을 헤더에 담아 로그인 요청을 보내도, 서버측에서 정상적으로 처리되지 않는 문제가 발생했다.
공식 문서에서 확인 가능한 정보들은 다음과 같다.
WebSocket을 공부하면서 HTTP 핸드셰이크를 위한 request 속성값들도 확인했었다. 해당 request에 사용자 정의 헤더 Authorization: Bearer {token}
을 추가하기만 하면 되는 간단한 문제라고 생각했는데, 문서에 따르면 WebSocket이 해당 기능을 제공하지 않고 있는 것 같았다..
공식문서에서 제시하는 다른 방법들은 다음과 같았다.
프로젝트의 사용자 인증 방식이 쿠키 기반으로 변경되어, 자연스럽게 로그인 관련 문제가 해결되게 되었다! 따라서 편리하게 Controller
에서 SpringSecurity에서 제공하는 사용자 정보를 사용할 수 있었다.
따로 적용하진 않았지만, 공식문서 기반으로 간략하게 찾아본 "방법2: STOMP 수준 헤더 사용" 방법은 아래와 같다.
STOMP 클라이언트를 사용해 연결 시 인증 헤더를 전달한다.
const socket = new SockJS('/ws');
const stompClient = Stomp.over(socket);
const headers = {
'Authorization': 'Bearer {token}'
};
stompClient.connect(headers, function(frame) {
// 연결 성공 시의 콜백
});
@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
ChannelInterceptor
를 통해 구현한 사용자 정의 인증 인터셉터가 SpringSecurity의 인터셉터보다 먼저 정렬되어 있는지 확인해야 한다.@Order(Ordered.HIGHEST_PRECEDENCE + 99)
이 애노테이션을 추가하면 된다고 한다.