본문 바로가기

Spring

[Spring Boot] Websocket 통신 시 JWT 인증 (interceptor 적용)

 

Interceptor 적용을 위해 configureClientInboundChannel 메서드를 재정의

 

@Configuration
@EnableWebSocketMessageBroker
class WebsocketConfig(
    private val env: Environment
) : WebSocketMessageBrokerConfigurer {

    //...
    
    /** interceptor 적용 **/
    override fun configureClientInboundChannel(registration: ChannelRegistration) {
        registration.interceptors(object : ChannelInterceptor {
            override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
                val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
                if (StompCommand.CONNECT == accessor?.command) {
                    // jwt 토큰 검증
                    val jwt = accessor.getNativeHeader(HttpHeaders.AUTHORIZATION)?.firstOrNull()
                        ?.replace("Bearer", "")
                        ?.replace(" ", "")
                        ?: throw AccessTokenNotValidException("invalid token")
                    if (!isJwtValid(jwt)) throw AccessTokenNotValidException("invalid token")
                    else super.preSend(message, channel)
                }
                return super.preSend(message, channel)
            }
        })
    }
    
    //...
}

 

ChannelInterceptor 인터페이스의 preSend 메서드 정의를 통해 메세지가 수신되기 전 메세지의 데이터 검증 및 변환을 할 수 있다.

  • MessageHeaderAccessor의 요청 명령어가 연결(CONNECT) 인치 확인
  • MessageHeaderAccessor의 getNativeHeader() 를 통해 클라이언트에서 연결 요청 시 보낸 header의 값을 확인할 수 있다.
  • Authorization 헤더의 값이 있는지 확인 후 jwt 검증을 통해 결과에 맞는 결과를 응답
  • 요청이 정상적인 검증이 되었다면 클라이언트에게 101 응답을 반환하게 된다.

* messageChannel은 기본적으로 스레드풀 사이즈가 1로 지정되어있어 production환경에서는 스레드풀 조정을 권장하고있다.

val executor = ThreadPoolTaskExecutor()

executor.corePoolSize = TASK_CORE_POOL_SIZE
executor.maxPoolSize = TASK_MAX_POOL_SIZE
executor.setQueueCapacity(TASK_QUEUE_CAPACITY)
executor.setThreadNamePrefix("websocket-thread-")
executor.setBeanName(EXECUTOR_BEAN_NAME)
executor.initialize()

registration.taskExecutor(executor)