Search code examples
springkotlinwebsocketspring-cloud-gateway

Websocket request not routed through gateway global filter


Problem

I have setup a global filter for all requests that adds user information to the headers. This filter works for all my http requests but doesnt apply them to my websocket requests.

Questions

  • Do websocket requests got through GlobalFilters?
  • If websocket requests do not go through GlobalFilters is there a way to specify a filter for websockets?
  • Are there any approaches that will allow me to inject user info into the websocket requests via spring-cloud-gateway?

Setup

Gateway configuration

    gateway:
      routes:
        - id: example
          uri: http://localhost:80
          predicates:
            - Path=/example/**
          filters:
            - RewritePath=/example/(?<path>.*), /$\{path}

GloablFilter

@Component
class CustomGlobalFilter(private val jwtDecoder: ReactiveJwtDecoder) : GlobalFilter {
    private val logger = KotlinLogging.logger {}

    /**
     * Given exchange, extract Authorization header value and modify to retrieve JWT token.
     */
    fun extractJwtToken(exchange: ServerWebExchange): String {
        return (exchange.request.headers["Authorization"]
            ?: throw JwtExtractionException("Request does not contain Authorization header"))[0]
            .replace("Bearer ", "")
    }

    /**
     * Modify request headers to add `username`.
     */
    fun modifyHeaders(exchange: ServerWebExchange): Mono<ServerWebExchange> {
        return try {
            val jwt = extractJwtToken(exchange)

            jwtDecoder
                .decode(jwt)
                .map {
                    val username = it.claims["username"] as String

                    val modRequest = exchange
                        .request
                        .mutate()
                        .header("username", username)
                        .build()

                    exchange.mutate()
                        .request(modRequest)
                        .build()
                }
        } catch (e: JwtExtractionException) {
            exchange.toMono() // fall back on default exchange
        }
    }

    /**
     * Filter all outgoing requests to modify headers.
     */
    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        return modifyHeaders(exchange)
            .flatMap { chain.filter(it) }
    }
}


Solution

  • I was able to fix the routing issue by setting the custom filter order before the WebsocketRoutingFilter

    @Component
    class CustomGlobalFilter(
            private val authFilterUtil: AuthFilterUtil,
    ) : GlobalFilter, Ordered {
    
        override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
            return authFilterUtil
                    .modifyHeaders(exchange)
                    .flatMap { chain.filter(it) }
        }
    
        override fun getOrder(): Int {
            return Ordered.HIGHEST_PRECEDENCE
        }
    }