Merge branch '4.2.x'
This commit is contained in:
@@ -109,6 +109,7 @@
|
||||
*** xref:spring-cloud-gateway-server-webmvc/filters/requestsize.adoc[]
|
||||
*** xref:spring-cloud-gateway-server-webmvc/filters/setrequesthostheader.adoc[]
|
||||
*** xref:spring-cloud-gateway-server-webmvc/filters/tokenrelay.adoc[]
|
||||
** xref:spring-cloud-gateway-server-webmvc/httpheadersfilters.adoc[]
|
||||
** xref:spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc[]
|
||||
** xref:spring-cloud-gateway-server-webmvc/working-with-servlets-and-filters.adoc[]
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
[[forwarded-headers-filter]]
|
||||
== Forwarded Headers Filter
|
||||
The `Forwarded` Headers Filter creates a `Forwarded` header to send to the downstream service. It adds the `Host` header, scheme and port of the current request to any existing `Forwarded` header.
|
||||
The `Forwarded` Headers Filter creates a `Forwarded` header to send to the downstream service. It adds the `Host` header, scheme and port of the current request to any existing `Forwarded` header. To activate this filter set the `spring.cloud.gateway.trusted-proxies` property to a Java Regular Expression. This regular expression defines the proxies that are trusted when they appear in the `Forwarded` header.
|
||||
|
||||
The `Forwarded by` header part can be enabled by setting the following property to true (defaults to false):
|
||||
|
||||
@@ -29,7 +29,7 @@ To change this, set the `spring.cloud.gateway.filter.remove-hop-by-hop.headers`
|
||||
|
||||
[[xforwarded-headers-filter]]
|
||||
== XForwarded Headers Filter
|
||||
The `XForwarded` Headers Filter creates various `X-Forwarded-*` headers to send to the downstream service. It uses the `Host` header, scheme, port and path of the current request to create the various headers.
|
||||
The `XForwarded` Headers Filter creates various `X-Forwarded-*` headers to send to the downstream service. It uses the `Host` header, scheme, port and path of the current request to create the various headers. To activate this filter set the `spring.cloud.gateway.trusted-proxies` property to a Java Regular Expression. This regular expression defines the proxies that are trusted when they appear in the `Forwarded` header.
|
||||
|
||||
Creating of individual headers can be controlled by the following boolean properties (defaults to true):
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
[[httpheadersfilters]]
|
||||
= HttpHeadersFilters
|
||||
|
||||
HttpHeadersFilters are applied to the requests before sending them downstream, such as in the `NettyRoutingFilter`.
|
||||
|
||||
[[forwarded-headers-filter]]
|
||||
== Forwarded Headers Filter
|
||||
The `Forwarded` Headers Filter creates a `Forwarded` header to send to the downstream service. It adds the `Host` header, scheme and port of the current request to any existing `Forwarded` header. To activate this filter set the `spring.cloud.gateway.mvc.trusted-proxies` property to a Java Regular Expression. This regular expression defines the proxies that are trusted when they appear in the `Forwarded` header.
|
||||
|
||||
[[removehopbyhop-headers-filter]]
|
||||
== RemoveHopByHop Headers Filter
|
||||
The `RemoveHopByHop` Headers Filter removes headers from forwarded requests. The default list of headers that is removed comes from the https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3[IETF].
|
||||
|
||||
.The default removed headers are:
|
||||
* Connection
|
||||
* Keep-Alive
|
||||
* Proxy-Authenticate
|
||||
* Proxy-Authorization
|
||||
* TE
|
||||
* Trailer
|
||||
* Transfer-Encoding
|
||||
* Upgrade
|
||||
|
||||
//To change this, set the `spring.cloud.gateway.filter.remove-hop-by-hop.headers` property to the list of header names to remove.
|
||||
|
||||
[[xforwarded-headers-filter]]
|
||||
== XForwarded Headers Filter
|
||||
The `XForwarded` Headers Filter creates various `X-Forwarded-*` headers to send to the downstream service. It uses the `Host` header, scheme, port and path of the current request to create the various headers. To activate this filter set the `spring.cloud.gateway.mvc.trusted-proxies` property to a Java Regular Expression. This regular expression defines the proxies that are trusted when they appear in the `Forwarded` header.
|
||||
|
||||
Creating of individual headers can be controlled by the following boolean properties (defaults to true):
|
||||
|
||||
- `spring.cloud.gateway.x-forwarded.for-enabled`
|
||||
- `spring.cloud.gateway.x-forwarded.host-enabled`
|
||||
- `spring.cloud.gateway.x-forwarded.port-enabled`
|
||||
- `spring.cloud.gateway.x-forwarded.proto-enabled`
|
||||
- `spring.cloud.gateway.x-forwarded.prefix-enabled`
|
||||
|
||||
Appending multiple headers can be controlled by the following boolean properties (defaults to true):
|
||||
|
||||
- `spring.cloud.gateway.x-forwarded.for-append`
|
||||
- `spring.cloud.gateway.x-forwarded.host-append`
|
||||
- `spring.cloud.gateway.x-forwarded.port-append`
|
||||
- `spring.cloud.gateway.x-forwarded.proto-append`
|
||||
- `spring.cloud.gateway.x-forwarded.prefix-append`
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|spring.cloud.gateway | |
|
||||
|spring.cloud.gateway | |
|
||||
|spring.cloud.gateway.default-filters | | List of filter definitions that are applied to every route.
|
||||
|spring.cloud.gateway.discovery.locator | |
|
||||
|spring.cloud.gateway.discovery.locator | |
|
||||
|spring.cloud.gateway.discovery.locator.enabled | `+++false+++` | Flag that enables DiscoveryClient gateway integration.
|
||||
|spring.cloud.gateway.discovery.locator.filters | |
|
||||
|spring.cloud.gateway.discovery.locator.include-expression | `+++true+++` | SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.
|
||||
@@ -21,10 +21,10 @@
|
||||
|spring.cloud.gateway.filter.fallback-headers.enabled | `+++true+++` | Enables the fallback-headers filter.
|
||||
|spring.cloud.gateway.filter.hystrix.enabled | `+++true+++` | Enables the hystrix filter.
|
||||
|spring.cloud.gateway.filter.json-to-grpc.enabled | `+++true+++` | Enables the JSON to gRPC filter.
|
||||
|spring.cloud.gateway.filter.local-response-cache | |
|
||||
|spring.cloud.gateway.filter.local-response-cache | |
|
||||
|spring.cloud.gateway.filter.local-response-cache.enabled | `+++false+++` | Enables the local-response-cache filter.
|
||||
|spring.cloud.gateway.filter.local-response-cache.request | |
|
||||
|spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.filter.local-response-cache.request | |
|
||||
|spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.filter.local-response-cache.size | | Maximum size of the cache to evict entries for this route (in KB, MB and GB).
|
||||
|spring.cloud.gateway.filter.local-response-cache.time-to-live | `+++5m+++` | Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
|
||||
|spring.cloud.gateway.filter.map-request-header.enabled | `+++true+++` | Enables the map-request-header filter.
|
||||
@@ -33,16 +33,16 @@
|
||||
|spring.cloud.gateway.filter.prefix-path.enabled | `+++true+++` | Enables the prefix-path filter.
|
||||
|spring.cloud.gateway.filter.preserve-host-header.enabled | `+++true+++` | Enables the preserve-host-header filter.
|
||||
|spring.cloud.gateway.filter.redirect-to.enabled | `+++true+++` | Enables the redirect-to filter.
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop.order | `+++0+++` |
|
||||
|spring.cloud.gateway.filter.remove-request-header.enabled | `+++true+++` | Enables the remove-request-header filter.
|
||||
|spring.cloud.gateway.filter.remove-request-parameter.enabled | `+++true+++` | Enables the remove-request-parameter filter.
|
||||
|spring.cloud.gateway.filter.remove-response-header.enabled | `+++true+++` | Enables the remove-response-header filter.
|
||||
|spring.cloud.gateway.filter.request-header-size.enabled | `+++true+++` | Enables the request-header-size filter.
|
||||
|spring.cloud.gateway.filter.request-header-to-request-uri.enabled | `+++true+++` | Enables the request-header-to-request-uri filter.
|
||||
|spring.cloud.gateway.filter.request-rate-limiter | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.default-rate-limiter | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.enabled | `+++true+++` | Enables the request-rate-limiter filter.
|
||||
|spring.cloud.gateway.filter.request-size.enabled | `+++true+++` | Enables the request-size filter.
|
||||
@@ -53,18 +53,18 @@
|
||||
|spring.cloud.gateway.filter.rewrite-request-parameter.enabled | `+++true+++` | Enables the rewrite-request-parameter filter.
|
||||
|spring.cloud.gateway.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|
||||
|spring.cloud.gateway.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|
||||
|spring.cloud.gateway.filter.secure-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.content-type-options | `+++nosniff+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.enabled | `+++true+++` | Enables the secure-headers filter.
|
||||
|spring.cloud.gateway.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|
||||
@@ -87,16 +87,16 @@
|
||||
|spring.cloud.gateway.global-filter.remove-cached-body.enabled | `+++true+++` | Enables the remove-cached-body global filter.
|
||||
|spring.cloud.gateway.global-filter.route-to-request-url.enabled | `+++true+++` | Enables the route-to-request-url global filter.
|
||||
|spring.cloud.gateway.global-filter.websocket-routing.enabled | `+++true+++` | Enables the websocket-routing global filter.
|
||||
|spring.cloud.gateway.globalcors | |
|
||||
|spring.cloud.gateway.globalcors | |
|
||||
|spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping | `+++false+++` | If global CORS config should be added to the URL handler.
|
||||
|spring.cloud.gateway.globalcors.cors-configurations | |
|
||||
|spring.cloud.gateway.handler-mapping.order | `+++1+++` | The order of RoutePredicateHandlerMapping.
|
||||
|spring.cloud.gateway.httpclient | |
|
||||
|spring.cloud.gateway.httpclient | |
|
||||
|spring.cloud.gateway.httpclient.compression | `+++false+++` | Enables compression for Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.connect-timeout | | The connect timeout in millis, the default is 30s.
|
||||
|spring.cloud.gateway.httpclient.max-header-size | | The max response header size.
|
||||
|spring.cloud.gateway.httpclient.max-initial-line-length | | The max initial line length.
|
||||
|spring.cloud.gateway.httpclient.pool | |
|
||||
|spring.cloud.gateway.httpclient.pool | |
|
||||
|spring.cloud.gateway.httpclient.pool.acquire-timeout | | Only for type FIXED, the maximum time in millis to wait for acquiring.
|
||||
|spring.cloud.gateway.httpclient.pool.eviction-interval | `+++0+++` | Perform regular eviction checks in the background at a specified interval. Disabled by default ({@link Duration#ZERO})
|
||||
|spring.cloud.gateway.httpclient.pool.leasing-strategy | `+++fifo+++` | Configures the leasing strategy for the pool (fifo or lifo), defaults to FIFO which is Netty's default.
|
||||
@@ -106,7 +106,7 @@
|
||||
|spring.cloud.gateway.httpclient.pool.metrics | `+++false+++` | Enables channel pools metrics to be collected and registered in Micrometer. Disabled by default.
|
||||
|spring.cloud.gateway.httpclient.pool.name | `+++proxy+++` | The channel pool map name, defaults to proxy.
|
||||
|spring.cloud.gateway.httpclient.pool.type | `+++elastic+++` | Type of pool for HttpClient to use (elastic, fixed or disabled).
|
||||
|spring.cloud.gateway.httpclient.proxy | |
|
||||
|spring.cloud.gateway.httpclient.proxy | |
|
||||
|spring.cloud.gateway.httpclient.proxy.host | | Hostname for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern | | Regular expression (Java) for a configured list of hosts. that should be reached directly, bypassing the proxy
|
||||
|spring.cloud.gateway.httpclient.proxy.password | | Password for proxy configuration of Netty HttpClient.
|
||||
@@ -114,7 +114,7 @@
|
||||
|spring.cloud.gateway.httpclient.proxy.type | `+++http+++` | proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5).
|
||||
|spring.cloud.gateway.httpclient.proxy.username | | Username for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.response-timeout | | The response timeout.
|
||||
|spring.cloud.gateway.httpclient.ssl | |
|
||||
|spring.cloud.gateway.httpclient.ssl | |
|
||||
|spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout | `+++3000ms+++` | SSL close_notify flush timeout. Default to 3000 ms.
|
||||
|spring.cloud.gateway.httpclient.ssl.close-notify-read-timeout | `+++0+++` | SSL close_notify read timeout. Default to 0 ms.
|
||||
|spring.cloud.gateway.httpclient.ssl.handshake-timeout | `+++10000ms+++` | SSL handshake timeout. Default to 10000 ms
|
||||
@@ -126,14 +126,14 @@
|
||||
|spring.cloud.gateway.httpclient.ssl.ssl-bundle | | The name of the SSL bundle to use.
|
||||
|spring.cloud.gateway.httpclient.ssl.trusted-x509-certificates | | Trusted certificates for verifying the remote endpoint's certificate.
|
||||
|spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager | `+++false+++` | Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production.
|
||||
|spring.cloud.gateway.httpclient.websocket | |
|
||||
|spring.cloud.gateway.httpclient.websocket | |
|
||||
|spring.cloud.gateway.httpclient.websocket.max-frame-payload-length | | Max frame payload length.
|
||||
|spring.cloud.gateway.httpclient.websocket.proxy-ping | `+++true+++` | Proxy ping frames to downstream services, defaults to true.
|
||||
|spring.cloud.gateway.httpclient.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpClient.
|
||||
|spring.cloud.gateway.httpserver.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpServer.
|
||||
|spring.cloud.gateway.loadbalancer | |
|
||||
|spring.cloud.gateway.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.metrics | |
|
||||
|spring.cloud.gateway.loadbalancer | |
|
||||
|spring.cloud.gateway.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.metrics | |
|
||||
|spring.cloud.gateway.metrics.enabled | `+++false+++` | Enables the collection of metrics data.
|
||||
|spring.cloud.gateway.metrics.prefix | `+++spring.cloud.gateway+++` | The prefix of all metrics emitted by gateway.
|
||||
|spring.cloud.gateway.metrics.tags | | Tags map that added to metrics.
|
||||
@@ -152,6 +152,7 @@
|
||||
|spring.cloud.gateway.mvc.streaming-buffer-size | `+++16384+++` | Buffer size for streaming media mime-types.
|
||||
|spring.cloud.gateway.mvc.streaming-media-types | | Mime-types that are streaming.
|
||||
|spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled | `+++true+++` | Enables the transfer-encoding-normalization-request-headers-filter.
|
||||
|spring.cloud.gateway.mvc.trusted-proxies | | Regular expression defining proxies that are trusted when they appear in a Forwarded of X-Forwarded header.
|
||||
|spring.cloud.gateway.mvc.weight-calculator-filter.enabled | `+++true+++` | Enables the weight-calculator-filter.
|
||||
|spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
@@ -181,7 +182,7 @@
|
||||
|spring.cloud.gateway.predicate.remote-addr.enabled | `+++true+++` | Enables the remote-addr predicate.
|
||||
|spring.cloud.gateway.predicate.weight.enabled | `+++true+++` | Enables the weight predicate.
|
||||
|spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled | `+++true+++` | Enables the xforwarded-remote-addr predicate.
|
||||
|spring.cloud.gateway.redis-rate-limiter | |
|
||||
|spring.cloud.gateway.redis-rate-limiter | |
|
||||
|spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | `+++X-RateLimit-Burst-Capacity+++` | The name of the header that returns the burst capacity configuration.
|
||||
|spring.cloud.gateway.redis-rate-limiter.config | |
|
||||
|spring.cloud.gateway.redis-rate-limiter.include-headers | `+++true+++` | Whether or not to include headers containing rate limiter information, defaults to true.
|
||||
@@ -195,10 +196,10 @@
|
||||
|spring.cloud.gateway.routes | | List of Routes.
|
||||
|spring.cloud.gateway.server.webflux.default-filters | | List of filter definitions that are applied to every route.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.enabled | `+++false+++` | Flag that enables DiscoveryClient gateway integration.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.filters | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.filters | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.include-expression | `+++true+++` | SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.lower-case-service-id | `+++false+++` | Option to lower case serviceId in predicates and filters, defaults to false. Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.predicates | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.predicates | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.route-id-prefix | | The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() + "_". Service Id will be appended to create the routeId.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.url-expression | `+++'lb://'+serviceId+++` | SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId.
|
||||
|spring.cloud.gateway.server.webflux.enabled | `+++true+++` | Enables gateway functionality.
|
||||
@@ -212,7 +213,7 @@
|
||||
|spring.cloud.gateway.server.webflux.filter.hystrix.enabled | `+++true+++` | Enables the hystrix filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.json-to-grpc.enabled | `+++true+++` | Enables the JSON to gRPC filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled | `+++false+++` | Enables the local-response-cache filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.size | | Maximum size of the cache to evict entries for this route (in KB, MB and GB).
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live | `+++5m+++` | Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
|
||||
|spring.cloud.gateway.server.webflux.filter.map-request-header.enabled | `+++true+++` | Enables the map-request-header filter.
|
||||
@@ -221,15 +222,15 @@
|
||||
|spring.cloud.gateway.server.webflux.filter.prefix-path.enabled | `+++true+++` | Enables the prefix-path filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.preserve-host-header.enabled | `+++true+++` | Enables the preserve-host-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.redirect-to.enabled | `+++true+++` | Enables the redirect-to filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.order | `+++0+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.order | `+++0+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-request-header.enabled | `+++true+++` | Enables the remove-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-request-parameter.enabled | `+++true+++` | Enables the remove-request-parameter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-response-header.enabled | `+++true+++` | Enables the remove-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-header-size.enabled | `+++true+++` | Enables the request-header-size filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-header-to-request-uri.enabled | `+++true+++` | Enables the request-header-to-request-uri filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-rate-limiter | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-rate-limiter | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.enabled | `+++true+++` | Enables the request-rate-limiter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-size.enabled | `+++true+++` | Enables the request-size filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.retry.enabled | `+++true+++` | Enables the retry filter.
|
||||
@@ -239,20 +240,20 @@
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-request-parameter.enabled | `+++true+++` | Enables the rewrite-request-parameter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-type-options | `+++nosniff+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-type-options | `+++nosniff+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.enabled | `+++true+++` | Enables the secure-headers filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.set-path.enabled | `+++true+++` | Enables the set-path filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-request-header.enabled | `+++true+++` | Enables the set-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled | `+++true+++` | Enables the set-request-host-header filter.
|
||||
@@ -273,7 +274,7 @@
|
||||
|spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled | `+++true+++` | Enables the route-to-request-url global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.websocket-routing.enabled | `+++true+++` | Enables the websocket-routing global filter.
|
||||
|spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping | `+++false+++` | If global CORS config should be added to the URL handler.
|
||||
|spring.cloud.gateway.server.webflux.globalcors.cors-configurations | |
|
||||
|spring.cloud.gateway.server.webflux.globalcors.cors-configurations | |
|
||||
|spring.cloud.gateway.server.webflux.handler-mapping.order | `+++1+++` | The order of RoutePredicateHandlerMapping.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.compression | `+++false+++` | Enables compression for Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.connect-timeout | | The connect timeout in millis, the default is 30s.
|
||||
@@ -310,7 +311,7 @@
|
||||
|spring.cloud.gateway.server.webflux.httpclient.websocket.proxy-ping | `+++true+++` | Proxy ping frames to downstream services, defaults to true.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpserver.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpServer.
|
||||
|spring.cloud.gateway.server.webflux.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.server.webflux.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.server.webflux.metrics.enabled | `+++false+++` | Enables the collection of metrics data.
|
||||
|spring.cloud.gateway.server.webflux.metrics.prefix | `+++spring.cloud.gateway+++` | The prefix of all metrics emitted by gateway.
|
||||
|spring.cloud.gateway.server.webflux.metrics.tags | | Tags map that added to metrics.
|
||||
@@ -331,7 +332,7 @@
|
||||
|spring.cloud.gateway.server.webflux.predicate.weight.enabled | `+++true+++` | Enables the weight predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.xforwarded-remote-addr.enabled | `+++true+++` | Enables the xforwarded-remote-addr predicate.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.burst-capacity-header | `+++X-RateLimit-Burst-Capacity+++` | The name of the header that returns the burst capacity configuration.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.config | |
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.config | |
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.include-headers | `+++true+++` | Whether or not to include headers containing rate limiter information, defaults to true.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.remaining-header | `+++X-RateLimit-Remaining+++` | The name of the header that returns number of remaining requests during the current second.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.replenish-rate-header | `+++X-RateLimit-Replenish-Rate+++` | The name of the header that returns the replenish rate configuration.
|
||||
@@ -342,7 +343,7 @@
|
||||
|spring.cloud.gateway.server.webflux.route-refresh-listener.enabled | `+++true+++` | If RouteRefreshListener should be turned on.
|
||||
|spring.cloud.gateway.server.webflux.routes | | List of Routes.
|
||||
|spring.cloud.gateway.server.webflux.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request.
|
||||
|spring.cloud.gateway.server.webflux.streaming-media-types | |
|
||||
|spring.cloud.gateway.server.webflux.streaming-media-types | |
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.for-enabled | `+++true+++` | If X-Forwarded-For is enabled.
|
||||
@@ -383,10 +384,10 @@
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.prefix-enabled | `+++true+++` | If X-Forwarded-Prefix is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-append | `+++true+++` | If appending X-Forwarded-Proto as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-enabled | `+++true+++` | If X-Forwarded-Proto is enabled.
|
||||
|spring.cloud.gateway.set-status | |
|
||||
|spring.cloud.gateway.set-status | |
|
||||
|spring.cloud.gateway.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request.
|
||||
|spring.cloud.gateway.streaming-media-types | |
|
||||
|spring.cloud.gateway.x-forwarded | |
|
||||
|spring.cloud.gateway.trusted-proxies | | Regular expression defining proxies that are trusted when they appear in a Forwarded or X-Forwarded header.
|
||||
|spring.cloud.gateway.x-forwarded.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.x-forwarded.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
|spring.cloud.gateway.x-forwarded.for-enabled | `+++true+++` | If X-Forwarded-For is enabled.
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopRequest
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopResponseHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.RemoveHttp2StatusResponseHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TrustedProxies;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties;
|
||||
@@ -59,6 +60,7 @@ import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFacto
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -120,10 +122,9 @@ public class GatewayServerMvcAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = GatewayMvcProperties.PREFIX, name = "forwarded-request-headers-filter.enabled",
|
||||
matchIfMissing = true)
|
||||
public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter() {
|
||||
return new ForwardedRequestHeadersFilter();
|
||||
@Conditional(TrustedProxies.ForwardedTrustedProxiesCondition.class)
|
||||
public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter(GatewayMvcProperties properties) {
|
||||
return new ForwardedRequestHeadersFilter(properties.getTrustedProxies());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -197,9 +198,10 @@ public class GatewayServerMvcAutoConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = XForwardedRequestHeadersFilterProperties.PREFIX, name = ".enabled",
|
||||
matchIfMissing = true)
|
||||
public XForwardedRequestHeadersFilter xForwardedRequestHeadersFilter(
|
||||
XForwardedRequestHeadersFilterProperties props) {
|
||||
return new XForwardedRequestHeadersFilter(props);
|
||||
@Conditional(TrustedProxies.XForwardedTrustedProxiesCondition.class)
|
||||
public XForwardedRequestHeadersFilter xForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties props,
|
||||
GatewayMvcProperties gatewayMvcProperties) {
|
||||
return new XForwardedRequestHeadersFilter(props, gatewayMvcProperties.getTrustedProxies());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -66,6 +66,12 @@ public class GatewayMvcProperties {
|
||||
*/
|
||||
private int streamingBufferSize = 16384;
|
||||
|
||||
/**
|
||||
* Regular expression defining proxies that are trusted when they appear in a
|
||||
* Forwarded of X-Forwarded header.
|
||||
*/
|
||||
private String trustedProxies;
|
||||
|
||||
public List<RouteProperties> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
@@ -102,6 +108,14 @@ public class GatewayMvcProperties {
|
||||
this.streamingBufferSize = streamingBufferSize;
|
||||
}
|
||||
|
||||
public String getTrustedProxies() {
|
||||
return trustedProxies;
|
||||
}
|
||||
|
||||
public void setTrustedProxies(String trustedProxies) {
|
||||
this.trustedProxies = trustedProxies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("httpClient", httpClient)
|
||||
@@ -109,6 +123,7 @@ public class GatewayMvcProperties {
|
||||
.append("routesMap", routesMap)
|
||||
.append("streamingMediaTypes", streamingMediaTypes)
|
||||
.append("streamingBufferSize", streamingBufferSize)
|
||||
.append("trustedProxies", trustedProxies)
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -86,7 +87,7 @@ public class GatewayMvcRuntimeHintsProcessor implements BeanFactoryInitializatio
|
||||
|
||||
private static Set<Class<?>> getTypesToRegister(String packageName) {
|
||||
Set<Class<?>> classesToAdd = new HashSet<>();
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
ClassPathScanningCandidateComponentProvider provider = buildProvider();
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(Object.class));
|
||||
provider.addExcludeFilter(new AssignableTypeFilter(FilterAutoConfiguration.class));
|
||||
provider.addExcludeFilter(new AssignableTypeFilter(PredicateAutoConfiguration.class));
|
||||
@@ -108,6 +109,17 @@ public class GatewayMvcRuntimeHintsProcessor implements BeanFactoryInitializatio
|
||||
return classesToAdd;
|
||||
}
|
||||
|
||||
private static ClassPathScanningCandidateComponentProvider buildProvider() {
|
||||
return new ClassPathScanningCandidateComponentProvider(false) {
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
// Include both concrete classes and interfaces
|
||||
return beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean shouldRegisterClass(Class<?> clazz) {
|
||||
Set<String> conditionClasses = beansConditionalOnClasses.getOrDefault(clazz.getName(), Collections.emptySet());
|
||||
for (String conditionClass : conditionClasses) {
|
||||
|
||||
@@ -24,7 +24,12 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
@@ -34,11 +39,26 @@ import org.springframework.web.servlet.function.ServerRequest;
|
||||
|
||||
public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ForwardedRequestHeadersFilter.class);
|
||||
|
||||
/**
|
||||
* Forwarded header.
|
||||
*/
|
||||
public static final String FORWARDED_HEADER = "Forwarded";
|
||||
|
||||
private final TrustedProxies trustedProxies;
|
||||
|
||||
@Deprecated
|
||||
public ForwardedRequestHeadersFilter() {
|
||||
trustedProxies = s -> true;
|
||||
log.warn(GatewayMvcProperties.PREFIX
|
||||
+ ".trusted-proxies is not set. Using deprecated Constructor. Untrusted hosts might be added to Forwarded header.");
|
||||
}
|
||||
|
||||
public ForwardedRequestHeadersFilter(String trustedProxiesRegex) {
|
||||
trustedProxies = TrustedProxies.from(trustedProxiesRegex);
|
||||
}
|
||||
|
||||
/* for testing */
|
||||
static List<Forwarded> parse(List<String> values) {
|
||||
ArrayList<Forwarded> forwardeds = new ArrayList<>();
|
||||
@@ -46,8 +66,11 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH
|
||||
return forwardeds;
|
||||
}
|
||||
for (String value : values) {
|
||||
Forwarded forwarded = parse(value);
|
||||
forwardeds.add(forwarded);
|
||||
String[] forwardedValues = StringUtils.tokenizeToStringArray(value, ",");
|
||||
for (String forwardedValue : forwardedValues) {
|
||||
Forwarded forwarded = parse(forwardedValue);
|
||||
forwardeds.add(forwarded);
|
||||
}
|
||||
}
|
||||
return forwardeds;
|
||||
}
|
||||
@@ -89,6 +112,13 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH
|
||||
|
||||
@Override
|
||||
public HttpHeaders apply(HttpHeaders input, ServerRequest request) {
|
||||
if (request.servletRequest().getRemoteAddr() != null
|
||||
&& !trustedProxies.isTrusted(request.servletRequest().getRemoteAddr())) {
|
||||
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
|
||||
request.servletRequest().getRemoteHost()));
|
||||
return input;
|
||||
}
|
||||
|
||||
HttpHeaders original = input;
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
@@ -102,7 +132,10 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH
|
||||
List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));
|
||||
|
||||
for (Forwarded f : forwardeds) {
|
||||
updated.add(FORWARDED_HEADER, f.toHeaderValue());
|
||||
// only add if "for" value matches trustedProxies
|
||||
if (trustedProxies.isTrusted(f.get("for"))) {
|
||||
updated.add(FORWARDED_HEADER, f.toHeaderValue());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add new forwarded
|
||||
@@ -124,6 +157,10 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH
|
||||
forValue = "[" + forValue + "]";
|
||||
}
|
||||
}
|
||||
if (!trustedProxies.isTrusted(forValue)) {
|
||||
// don't add for value
|
||||
return;
|
||||
}
|
||||
int port = remoteAddress.getPort();
|
||||
if (port >= 0) {
|
||||
forValue = forValue + ":" + port;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.filter;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TrustedProxies {
|
||||
|
||||
boolean isTrusted(String host);
|
||||
|
||||
static TrustedProxies from(@NonNull String trustedProxies) {
|
||||
Assert.hasText(trustedProxies, "trustedProxies must not be empty");
|
||||
Pattern pattern = Pattern.compile(trustedProxies);
|
||||
return value -> pattern.matcher(value).matches();
|
||||
}
|
||||
|
||||
class ForwardedTrustedProxiesCondition extends AllNestedConditions {
|
||||
|
||||
public ForwardedTrustedProxiesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".forwarded-request-headers-filter.enabled",
|
||||
matchIfMissing = true)
|
||||
static class OnPropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnPropertyExists(GatewayMvcProperties.PREFIX + ".trusted-proxies")
|
||||
static class OnTrustedProxiesNotEmpty {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class XForwardedTrustedProxiesCondition extends AllNestedConditions {
|
||||
|
||||
public XForwardedTrustedProxiesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = XForwardedRequestHeadersFilterProperties.PREFIX + ".enabled",
|
||||
matchIfMissing = true)
|
||||
static class OnPropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnPropertyExists(GatewayMvcProperties.PREFIX + ".trusted-proxies")
|
||||
static class OnTrustedProxiesNotEmpty {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class OnPropertyExistsCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
try {
|
||||
String value = metadata.getAnnotations().get(ConditionalOnPropertyExists.class).getString("value");
|
||||
String property = context.getEnvironment().getProperty(value);
|
||||
if (!StringUtils.hasText(property)) {
|
||||
return ConditionOutcome.noMatch(value + " property is not set or is empty.");
|
||||
}
|
||||
return ConditionOutcome.match(value + " property is not empty.");
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return ConditionOutcome.noMatch("Missing required property 'value' of @ConditionalOnPropertyExists");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@Conditional(OnPropertyExistsCondition.class)
|
||||
@interface ConditionalOnPropertyExists {
|
||||
|
||||
/**
|
||||
* @return the property
|
||||
*/
|
||||
String value();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,21 +16,29 @@
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.filter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
|
||||
public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered {
|
||||
|
||||
private static final Log log = LogFactory.getLog(XForwardedRequestHeadersFilter.class);
|
||||
|
||||
/** Default http port. */
|
||||
public static final int HTTP_PORT = 80;
|
||||
|
||||
@@ -60,8 +68,24 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
|
||||
private final XForwardedRequestHeadersFilterProperties properties;
|
||||
|
||||
private final TrustedProxies trustedProxies;
|
||||
|
||||
@Deprecated
|
||||
public XForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties properties) {
|
||||
this.properties = properties;
|
||||
this(properties, s -> true);
|
||||
log.warn(GatewayMvcProperties.PREFIX
|
||||
+ ".trusted-proxies is not set. Using deprecated Constructor. Untrusted hosts might be added to X-Forwarded header.");
|
||||
}
|
||||
|
||||
public XForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties props, String trustedProxies) {
|
||||
this(props, TrustedProxies.from(trustedProxies));
|
||||
}
|
||||
|
||||
private XForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties props,
|
||||
TrustedProxies trustedProxies) {
|
||||
Assert.notNull(trustedProxies, "trustedProxies must not be null");
|
||||
|
||||
this.trustedProxies = trustedProxies;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,6 +95,13 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
|
||||
@Override
|
||||
public HttpHeaders apply(HttpHeaders input, ServerRequest request) {
|
||||
if (request.servletRequest().getRemoteAddr() != null
|
||||
&& !trustedProxies.isTrusted(request.servletRequest().getRemoteAddr())) {
|
||||
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
|
||||
request.servletRequest().getRemoteHost()));
|
||||
return input;
|
||||
}
|
||||
|
||||
HttpHeaders original = input;
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
@@ -78,10 +109,12 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
updated.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
InetSocketAddress remoteAddress = request.remoteAddress().orElse(null);
|
||||
if (properties.isForEnabled() && remoteAddress != null && remoteAddress.getAddress() != null) {
|
||||
String remoteAddr = remoteAddress.getAddress().getHostAddress();
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, properties.isForAppend());
|
||||
if (properties.isForEnabled()) {
|
||||
String remoteAddr = null;
|
||||
if (request.servletRequest().getRemoteAddr() != null) {
|
||||
remoteAddr = request.servletRequest().getRemoteAddr();
|
||||
}
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, properties.isForAppend(), trustedProxies::isTrusted);
|
||||
}
|
||||
|
||||
String proto = request.uri().getScheme();
|
||||
@@ -156,17 +189,22 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
}
|
||||
|
||||
private void write(HttpHeaders headers, String name, String value, boolean append) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
write(headers, name, value, append, s -> true);
|
||||
}
|
||||
|
||||
private void write(HttpHeaders headers, String name, String value, boolean append, Predicate<String> shouldWrite) {
|
||||
if (append) {
|
||||
headers.add(name, value);
|
||||
if (value != null) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
// these headers should be treated as a single comma separated header
|
||||
List<String> values = headers.get(name);
|
||||
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
|
||||
headers.set(name, delimitedValue);
|
||||
if (headers.containsKey(name)) {
|
||||
List<String> values = headers.get(name).stream().filter(shouldWrite).toList();
|
||||
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
|
||||
headers.set(name, delimitedValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (value != null && shouldWrite.test(value)) {
|
||||
headers.set(name, value);
|
||||
}
|
||||
}
|
||||
@@ -175,11 +213,6 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
return HTTPS_SCHEME.equals(scheme) ? HTTPS_PORT : HTTP_PORT;
|
||||
}
|
||||
|
||||
private boolean hasHeader(ServerRequest request, String name) {
|
||||
HttpHeaders headers = request.headers().asHttpHeaders();
|
||||
return headers.containsKey(name) && StringUtils.hasLength(headers.getFirst(name));
|
||||
}
|
||||
|
||||
private String toHostHeader(ServerRequest request) {
|
||||
int port = request.uri().getPort();
|
||||
String host = request.uri().getHost();
|
||||
|
||||
@@ -588,7 +588,7 @@ public class ServerMvcIntegrationTests {
|
||||
.isOk();
|
||||
}
|
||||
|
||||
public static final MediaType FORM_URL_ENCODED_CONTENT_TYPE = new MediaType(APPLICATION_FORM_URLENCODED,
|
||||
private static final MediaType FORM_URL_ENCODED_CONTENT_TYPE = new MediaType(APPLICATION_FORM_URLENCODED,
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -61,7 +62,7 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter();
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
@@ -81,28 +82,39 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
public void forwardedHeaderExists() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.remoteAddress("10.0.0.1:80")
|
||||
.header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https; for=23.45.67.89")
|
||||
.header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter();
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers.get(FORWARDED_HEADER)).hasSize(2);
|
||||
assertThat(headers.get(FORWARDED_HEADER)).hasSize(3);
|
||||
|
||||
List<Forwarded> forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER));
|
||||
|
||||
assertThat(forwardeds).hasSize(2);
|
||||
Forwarded addedForwardedHeader = forwardeds.get(0);
|
||||
Forwarded existingForwardedHeader = forwardeds.get(1);
|
||||
|
||||
assertThat(existingForwardedHeader.getValues()).containsEntry("proto", "http")
|
||||
.containsEntry("for", "\"10.0.0.1:80\"");
|
||||
|
||||
assertThat(addedForwardedHeader.getValues()).containsEntry("proto", "https")
|
||||
.containsEntry("for", "23.45.67.89");
|
||||
assertThat(forwardeds).hasSize(3);
|
||||
Optional<Forwarded> added = forwardeds.stream()
|
||||
.filter(forwarded -> forwarded.get("for").contains("10.0.0.1:80"))
|
||||
.findFirst();
|
||||
assertThat(added).isPresent();
|
||||
added.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", "\"10.0.0.1:80\"");
|
||||
});
|
||||
Optional<Forwarded> existing = forwardeds.stream()
|
||||
.filter(forwarded -> forwarded.get("for").equals("23.45.67.89"))
|
||||
.findFirst();
|
||||
assertThat(existing).isPresent();
|
||||
existing.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("for", "23.45.67.89");
|
||||
});
|
||||
existing = forwardeds.stream().filter(forwarded -> forwarded.get("for").equals("12.34.56.78")).findFirst();
|
||||
assertThat(existing).isPresent();
|
||||
existing.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("proto", "https").containsEntry("for", "12.34.56.78");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,7 +125,7 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter();
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
@@ -136,7 +148,7 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
servletRequest.setRemoteHost("2001:db8:cafe:0:0:0:0:17");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter();
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
@@ -158,7 +170,7 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
servletRequest.setRemoteHost("unresolvable-hostname");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter();
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
@@ -211,4 +223,62 @@ public class ForwardedRequestHeadersFilterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedHeadersNotTrusted() throws Exception {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.remoteAddress("10.0.0.1:80")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter("11\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).doesNotContainKeys(FORWARDED_HEADER);
|
||||
}
|
||||
|
||||
// verify that existing forwarded header is not forwarded if not trusted
|
||||
@Test
|
||||
public void untrustedForwardedForNotAppended() throws Exception {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.remoteAddress("10.0.0.1:80")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(FORWARDED_HEADER, "proto=http;host=myhost;for=\"127.0.0.1:80\",for=10.0.0.11")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).containsKeys(FORWARDED_HEADER);
|
||||
List<String> forwardedHeaders = headers.get(FORWARDED_HEADER);
|
||||
Optional<String> filtered = forwardedHeaders.stream().filter(value -> value.contains("127.0.0.1")).findFirst();
|
||||
assertThat(filtered).isEmpty();
|
||||
filtered = forwardedHeaders.stream().filter(value -> value.contains("10.0.0.11")).findFirst();
|
||||
assertThat(filtered).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteAdddressIsNullUnTrustedProxyNotAppended() throws Exception {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(FORWARDED_HEADER, "proto=http;host=myhost;for=127.0.0.1")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteAddr(null);
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).containsKeys(FORWARDED_HEADER);
|
||||
List<String> forwardedHeaders = headers.get(FORWARDED_HEADER);
|
||||
Optional<String> filtered = forwardedHeaders.stream().filter(value -> value.contains("127.0.0.1")).findFirst();
|
||||
assertThat(filtered).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.server.mvc.filter;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class XForwardedRequestHeadersFilterTests {
|
||||
|
||||
@Test
|
||||
public void remoteAddressIsNull() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteAddr(null);
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
XForwardedRequestHeadersFilter filter = new XForwardedRequestHeadersFilter(
|
||||
new XForwardedRequestHeadersFilterProperties(), ".*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).doesNotContainKeys(X_FORWARDED_FOR_HEADER)
|
||||
.containsKeys(X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("myhost");
|
||||
assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("80");
|
||||
assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionMatches() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
SslAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
|
||||
GatewayServerMvcAutoConfiguration.class))
|
||||
.withPropertyValues(GatewayMvcProperties.PREFIX + ".trusted-proxies=11\\.0\\.0\\..*")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(XForwardedRequestHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionDoesNotMatch() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
SslAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
|
||||
GatewayServerMvcAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(XForwardedRequestHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyTrustedProxiesFails() {
|
||||
Assertions
|
||||
.assertThatThrownBy(
|
||||
() -> new XForwardedRequestHeadersFilter(new XForwardedRequestHeadersFilterProperties(), ""))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xForwardedHeadersNotTrusted() throws Exception {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.remoteAddress("10.0.0.1:80")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
XForwardedRequestHeadersFilter filter = new XForwardedRequestHeadersFilter(
|
||||
new XForwardedRequestHeadersFilterProperties(), "11\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).doesNotContainKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
}
|
||||
|
||||
// verify that existing forwarded header is not forwarded if not trusted
|
||||
@Test
|
||||
public void untrustedXForwardedForNotAppended() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.remoteAddress("10.0.0.1:80")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(X_FORWARDED_FOR_HEADER, "127.0.0.1")
|
||||
.header(X_FORWARDED_FOR_HEADER, "10.0.0.10")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteHost("10.0.0.1");
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
XForwardedRequestHeadersFilter filter = new XForwardedRequestHeadersFilter(
|
||||
new XForwardedRequestHeadersFilterProperties(), "10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).doesNotContain("127.0.0.1")
|
||||
.contains("10.0.0.1", "10.0.0.10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteAdddressIsNullUnTrustedProxyNotAppended() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(X_FORWARDED_FOR_HEADER, "127.0.0.1")
|
||||
.buildRequest(null);
|
||||
servletRequest.setRemoteAddr(null);
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
XForwardedRequestHeadersFilter filter = new XForwardedRequestHeadersFilter(
|
||||
new XForwardedRequestHeadersFilterProperties(), "10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
|
||||
|
||||
assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).doesNotContain("127.0.0.1");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,7 +22,11 @@ import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestUtils {
|
||||
public final class TestUtils {
|
||||
|
||||
private TestUtils() {
|
||||
|
||||
}
|
||||
|
||||
public static Map<String, Object> getMap(Map<String, Object> map, String mapKey) {
|
||||
assertThat(map).isNotEmpty().containsKey(mapKey);
|
||||
|
||||
@@ -6,4 +6,8 @@ logging:
|
||||
org.springframework.retry: TRACE
|
||||
spring:
|
||||
mvc:
|
||||
log-request-details: true
|
||||
log-request-details: true
|
||||
cloud:
|
||||
gateway:
|
||||
mvc:
|
||||
trusted-proxies: .*
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020-2023 VMware, Inc. or its affiliates, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import reactor.netty.http.server.ConnectionInfo;
|
||||
import reactor.netty.transport.AddressUtils;
|
||||
|
||||
import static reactor.netty.http.server.ConnectionInfo.getDefaultHostPort;
|
||||
|
||||
/**
|
||||
* Default implementation for handling {@code X-Forwarded}/{@code Forwarded} headers.
|
||||
*
|
||||
* @author Andrey Shlykov
|
||||
* @since 0.9.12
|
||||
*/
|
||||
final class DefaultNettyHttpForwardedHeaderHandler implements BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> {
|
||||
|
||||
static final DefaultNettyHttpForwardedHeaderHandler INSTANCE = new DefaultNettyHttpForwardedHeaderHandler();
|
||||
|
||||
static final String FORWARDED_HEADER = "Forwarded";
|
||||
static final String X_FORWARDED_IP_HEADER = "X-Forwarded-For";
|
||||
static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host";
|
||||
static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port";
|
||||
static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";
|
||||
|
||||
static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?");
|
||||
static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?");
|
||||
static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("for=\"?([^;,\"]+)\"?");
|
||||
|
||||
/**
|
||||
* Specifies whether the Http Server applies a strict {@code Forwarded} header
|
||||
* validation. By default, it is enabled and strict validation is used.
|
||||
* @since 1.0.8
|
||||
* @deprecated The system property is used for backwards compatibility and will be
|
||||
* removed in version 1.2.0.
|
||||
*/
|
||||
@Deprecated
|
||||
static final String FORWARDED_HEADER_VALIDATION = "reactor.netty.http.server.forwarded.strictValidation";
|
||||
static final boolean DEFAULT_FORWARDED_HEADER_VALIDATION = Boolean
|
||||
.parseBoolean(System.getProperty(FORWARDED_HEADER_VALIDATION, "true"));
|
||||
|
||||
@Override
|
||||
public ConnectionInfo apply(ConnectionInfo connectionInfo, HttpRequest request) {
|
||||
String forwardedHeader = request.headers().get(FORWARDED_HEADER);
|
||||
if (forwardedHeader != null) {
|
||||
return parseForwardedInfo(connectionInfo, forwardedHeader);
|
||||
}
|
||||
return parseXForwardedInfo(connectionInfo, request);
|
||||
}
|
||||
|
||||
private ConnectionInfo parseForwardedInfo(ConnectionInfo connectionInfo, String forwardedHeader) {
|
||||
String forwarded = forwardedHeader.split(",", 2)[0];
|
||||
Matcher protoMatcher = FORWARDED_PROTO_PATTERN.matcher(forwarded);
|
||||
if (protoMatcher.find()) {
|
||||
connectionInfo = connectionInfo.withScheme(protoMatcher.group(1).trim());
|
||||
}
|
||||
Matcher hostMatcher = FORWARDED_HOST_PATTERN.matcher(forwarded);
|
||||
if (hostMatcher.find()) {
|
||||
connectionInfo = connectionInfo.withHostAddress(AddressUtils.parseAddress(hostMatcher.group(1),
|
||||
getDefaultHostPort(connectionInfo.getScheme()), DEFAULT_FORWARDED_HEADER_VALIDATION));
|
||||
}
|
||||
Matcher forMatcher = FORWARDED_FOR_PATTERN.matcher(forwarded);
|
||||
if (forMatcher.find()) {
|
||||
connectionInfo = connectionInfo.withRemoteAddress(AddressUtils.parseAddress(forMatcher.group(1).trim(),
|
||||
connectionInfo.getRemoteAddress().getPort(), DEFAULT_FORWARDED_HEADER_VALIDATION));
|
||||
}
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
private ConnectionInfo parseXForwardedInfo(ConnectionInfo connectionInfo, HttpRequest request) {
|
||||
String ipHeader = request.headers().get(X_FORWARDED_IP_HEADER);
|
||||
if (ipHeader != null) {
|
||||
connectionInfo = connectionInfo.withRemoteAddress(
|
||||
AddressUtils.parseAddress(ipHeader.split(",", 2)[0], connectionInfo.getRemoteAddress().getPort()));
|
||||
}
|
||||
String protoHeader = request.headers().get(X_FORWARDED_PROTO_HEADER);
|
||||
if (protoHeader != null) {
|
||||
connectionInfo = connectionInfo.withScheme(protoHeader.split(",", 2)[0].trim());
|
||||
}
|
||||
String hostHeader = request.headers().get(X_FORWARDED_HOST_HEADER);
|
||||
if (hostHeader != null) {
|
||||
connectionInfo = connectionInfo
|
||||
.withHostAddress(AddressUtils.parseAddress(hostHeader.split(",", 2)[0].trim(),
|
||||
getDefaultHostPort(connectionInfo.getScheme()), DEFAULT_FORWARDED_HEADER_VALIDATION));
|
||||
}
|
||||
|
||||
String portHeader = request.headers().get(X_FORWARDED_PORT_HEADER);
|
||||
if (portHeader != null && !portHeader.isEmpty()) {
|
||||
String portStr = portHeader.split(",", 2)[0].trim();
|
||||
if (portStr.chars().allMatch(Character::isDigit)) {
|
||||
int port = Integer.parseInt(portStr);
|
||||
connectionInfo = connectionInfo.withHostAddress(
|
||||
AddressUtils.createUnresolved(connectionInfo.getHostAddress().getHostString(), port),
|
||||
connectionInfo.getHostName(), port);
|
||||
}
|
||||
else if (DEFAULT_FORWARDED_HEADER_VALIDATION) {
|
||||
throw new IllegalArgumentException("Failed to parse a port from " + portHeader);
|
||||
}
|
||||
}
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -62,6 +63,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
|
||||
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
|
||||
import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint;
|
||||
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter;
|
||||
@@ -127,6 +129,7 @@ import org.springframework.cloud.gateway.filter.headers.GRPCResponseHeadersFilte
|
||||
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.TransferEncodingNormalizationHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.TrustedProxies;
|
||||
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.Bucket4jRateLimiter;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
|
||||
@@ -319,11 +322,11 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.forwarded.enabled", matchIfMissing = true)
|
||||
public ForwardedHeadersFilter forwardedHeadersFilter(Environment env, ServerProperties serverProperties) {
|
||||
@Conditional(TrustedProxies.ForwardedTrustedProxiesCondition.class)
|
||||
public ForwardedHeadersFilter forwardedHeadersFilter(Environment env, ServerProperties serverProperties, GatewayProperties properties) {
|
||||
boolean forwardedByEnabled = env.getProperty("spring.cloud.gateway.server.webflux.forwarded.by.enabled",
|
||||
Boolean.class, false);
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter(properties.getTrustedProxies());
|
||||
forwardedHeadersFilter.setForwardedByEnabled(forwardedByEnabled);
|
||||
forwardedHeadersFilter.setServerPort(serverProperties.getPort());
|
||||
return forwardedHeadersFilter;
|
||||
@@ -337,9 +340,9 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.x-forwarded.enabled", matchIfMissing = true)
|
||||
public XForwardedHeadersFilter xForwardedHeadersFilter() {
|
||||
return new XForwardedHeadersFilter();
|
||||
@Conditional(TrustedProxies.XForwardedTrustedProxiesCondition.class)
|
||||
public XForwardedHeadersFilter xForwardedHeadersFilter(GatewayProperties properties) {
|
||||
return new XForwardedHeadersFilter(properties.getTrustedProxies());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -790,6 +793,21 @@ public class GatewayAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@TrustedProxies.ConditionalOnPropertyExists
|
||||
public NettyServerCustomizer gatewayNettyServerCustomizer(GatewayProperties gatewayProperties) {
|
||||
TrustedProxies trustedProxies = TrustedProxies.from(gatewayProperties.getTrustedProxies());
|
||||
|
||||
return httpServer -> httpServer.forwarded((connectionInfo, httpRequest) -> {
|
||||
InetSocketAddress remoteAddress = connectionInfo.getRemoteAddress();
|
||||
if (remoteAddress != null && trustedProxies.isTrusted(remoteAddress.getHostString())) {
|
||||
// update remote address
|
||||
return DefaultNettyHttpForwardedHeaderHandler.INSTANCE.apply(connectionInfo, httpRequest);
|
||||
}
|
||||
return connectionInfo;
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpClientSslConfigurer httpClientSslConfigurer(ServerProperties serverProperties,
|
||||
HttpClientProperties httpClientProperties, SslBundles bundles) {
|
||||
|
||||
@@ -73,6 +73,12 @@ public class GatewayProperties {
|
||||
*/
|
||||
private boolean routeFilterCacheEnabled = false;
|
||||
|
||||
/**
|
||||
* Regular expression defining proxies that are trusted when they appear in a
|
||||
* Forwarded or X-Forwarded header.
|
||||
*/
|
||||
private String trustedProxies;
|
||||
|
||||
public boolean isRouteFilterCacheEnabled() {
|
||||
return routeFilterCacheEnabled;
|
||||
}
|
||||
@@ -116,6 +122,14 @@ public class GatewayProperties {
|
||||
this.failOnRouteDefinitionError = failOnRouteDefinitionError;
|
||||
}
|
||||
|
||||
public String getTrustedProxies() {
|
||||
return trustedProxies;
|
||||
}
|
||||
|
||||
public void setTrustedProxies(String trustedProxies) {
|
||||
this.trustedProxies = trustedProxies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("routes", routes)
|
||||
@@ -123,6 +137,7 @@ public class GatewayProperties {
|
||||
.append("streamingMediaTypes", streamingMediaTypes)
|
||||
.append("failOnRouteDefinitionError", failOnRouteDefinitionError)
|
||||
.append("routeFilterCacheEnabled", routeFilterCacheEnabled)
|
||||
.append("trustedProxies", trustedProxies)
|
||||
.toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ import java.util.Map;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -44,6 +46,8 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*/
|
||||
public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ForwardedHeadersFilter.class);
|
||||
|
||||
private Integer serverPort;
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
@@ -55,6 +59,19 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
*/
|
||||
public static final String FORWARDED_HEADER = "Forwarded";
|
||||
|
||||
private final TrustedProxies trustedProxies;
|
||||
|
||||
@Deprecated
|
||||
public ForwardedHeadersFilter() {
|
||||
trustedProxies = s -> true;
|
||||
log.warn(GatewayProperties.PREFIX
|
||||
+ ".trusted-proxies is not set. Using deprecated Constructor. Untrusted hosts might be added to Forwarded header.");
|
||||
}
|
||||
|
||||
public ForwardedHeadersFilter(String trustedProxiesRegex) {
|
||||
trustedProxies = TrustedProxies.from(trustedProxiesRegex);
|
||||
}
|
||||
|
||||
/* for testing */
|
||||
static List<Forwarded> parse(List<String> values) {
|
||||
ArrayList<Forwarded> forwardeds = new ArrayList<>();
|
||||
@@ -62,8 +79,11 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
return forwardeds;
|
||||
}
|
||||
for (String value : values) {
|
||||
Forwarded forwarded = parse(value);
|
||||
forwardeds.add(forwarded);
|
||||
String[] forwardedValues = StringUtils.tokenizeToStringArray(value, ",");
|
||||
for (String forwardedValue : forwardedValues) {
|
||||
Forwarded forwarded = parse(forwardedValue);
|
||||
forwardeds.add(forwarded);
|
||||
}
|
||||
}
|
||||
return forwardeds;
|
||||
}
|
||||
@@ -114,6 +134,14 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
@Override
|
||||
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
|
||||
if (request.getRemoteAddress() != null
|
||||
&& !trustedProxies.isTrusted(request.getRemoteAddress().getHostString())) {
|
||||
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
|
||||
request.getRemoteAddress()));
|
||||
return input;
|
||||
}
|
||||
|
||||
HttpHeaders original = input;
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
@@ -127,7 +155,10 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));
|
||||
|
||||
for (Forwarded f : forwardeds) {
|
||||
updated.add(FORWARDED_HEADER, f.toHeaderValue());
|
||||
// only add if "for" value matches trustedProxies
|
||||
if (trustedProxies.isTrusted(f.get("for"))) {
|
||||
updated.add(FORWARDED_HEADER, f.toHeaderValue());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add new forwarded
|
||||
@@ -136,6 +167,7 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
Forwarded forwarded = new Forwarded().put("host", host).put("proto", uri.getScheme());
|
||||
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
// TODO: only add if "remoteAddress" value matches trustedProxies
|
||||
if (remoteAddress != null) {
|
||||
// If remoteAddress is unresolved, calling getHostAddress() would cause a
|
||||
// NullPointerException.
|
||||
@@ -150,11 +182,14 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
forValue = "[" + forValue + "]";
|
||||
}
|
||||
}
|
||||
int port = remoteAddress.getPort();
|
||||
if (port >= 0) {
|
||||
forValue = forValue + ":" + port;
|
||||
if (trustedProxies.isTrusted(forValue)) {
|
||||
// only add for value if trusted
|
||||
int port = remoteAddress.getPort();
|
||||
if (port >= 0) {
|
||||
forValue = forValue + ":" + port;
|
||||
}
|
||||
forwarded.put("for", forValue);
|
||||
}
|
||||
forwarded.put("for", forValue);
|
||||
}
|
||||
|
||||
if (forwardedByEnabled) {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2013-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.filter.headers;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TrustedProxies {
|
||||
|
||||
/**
|
||||
* Property name.
|
||||
*/
|
||||
String PROPERTY = GatewayProperties.PREFIX + ".trusted-proxies";
|
||||
|
||||
boolean isTrusted(String host);
|
||||
|
||||
static TrustedProxies from(@NonNull String trustedProxies) {
|
||||
Assert.hasText(trustedProxies, "trustedProxies must not be empty");
|
||||
Pattern pattern = Pattern.compile(trustedProxies);
|
||||
return value -> pattern.matcher(value).matches();
|
||||
}
|
||||
|
||||
class ForwardedTrustedProxiesCondition extends AllNestedConditions {
|
||||
|
||||
public ForwardedTrustedProxiesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".forwarded.enabled", matchIfMissing = true)
|
||||
static class OnPropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnPropertyExists
|
||||
static class OnTrustedProxiesNotEmpty {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class XForwardedTrustedProxiesCondition extends AllNestedConditions {
|
||||
|
||||
public XForwardedTrustedProxiesCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".x-forwarded.enabled", matchIfMissing = true)
|
||||
static class OnPropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnPropertyExists
|
||||
static class OnTrustedProxiesNotEmpty {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class OnPropertyExistsCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
try {
|
||||
String property = context.getEnvironment().getProperty(PROPERTY);
|
||||
if (!StringUtils.hasText(property)) {
|
||||
return ConditionOutcome.noMatch(PROPERTY + " property is not set or is empty.");
|
||||
}
|
||||
return ConditionOutcome.match(PROPERTY + " property is not empty.");
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return ConditionOutcome.noMatch("Missing required property 'value' of @ConditionalOnPropertyExists");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Documented
|
||||
@Conditional(OnPropertyExistsCondition.class)
|
||||
@interface ConditionalOnPropertyExists {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,9 +20,15 @@ import java.net.URI;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -35,6 +41,8 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
|
||||
@ConfigurationProperties("spring.cloud.gateway.server.webflux.x-forwarded")
|
||||
public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
|
||||
private static final Log log = LogFactory.getLog(XForwardedHeadersFilter.class);
|
||||
|
||||
/** Default http port. */
|
||||
public static final int HTTP_PORT = 80;
|
||||
|
||||
@@ -98,6 +106,19 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
/** If appending X-Forwarded-Prefix as a list is enabled. */
|
||||
private boolean prefixAppend = true;
|
||||
|
||||
private final TrustedProxies trustedProxies;
|
||||
|
||||
@Deprecated
|
||||
public XForwardedHeadersFilter() {
|
||||
trustedProxies = s -> true;
|
||||
log.warn(GatewayProperties.PREFIX
|
||||
+ ".trusted-proxies is not set. Using deprecated Constructor. Untrusted hosts might be added to Forwarded header.");
|
||||
}
|
||||
|
||||
public XForwardedHeadersFilter(String trustedProxiesRegex) {
|
||||
trustedProxies = TrustedProxies.from(trustedProxiesRegex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
@@ -197,8 +218,15 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
|
||||
if (request.getRemoteAddress() != null
|
||||
&& !trustedProxies.isTrusted(request.getRemoteAddress().getHostString())) {
|
||||
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
|
||||
request.getRemoteAddress()));
|
||||
return input;
|
||||
}
|
||||
|
||||
HttpHeaders original = input;
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
@@ -206,9 +234,13 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
updated.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (isForEnabled() && request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null) {
|
||||
String remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend());
|
||||
if (isForEnabled()) {
|
||||
String remoteAddr = null;
|
||||
if (request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null) {
|
||||
remoteAddr = request.getRemoteAddress().getHostString();
|
||||
}
|
||||
// match xforwarded for against trusted proxies
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend(), trustedProxies::isTrusted);
|
||||
}
|
||||
|
||||
String proto = request.getURI().getScheme();
|
||||
@@ -284,17 +316,22 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
}
|
||||
|
||||
private void write(HttpHeaders headers, String name, String value, boolean append) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
write(headers, name, value, append, s -> true);
|
||||
}
|
||||
|
||||
private void write(HttpHeaders headers, String name, String value, boolean append, Predicate<String> shouldWrite) {
|
||||
if (append) {
|
||||
headers.add(name, value);
|
||||
if (value != null) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
// these headers should be treated as a single comma separated header
|
||||
List<String> values = headers.get(name);
|
||||
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
|
||||
headers.set(name, delimitedValue);
|
||||
if (headers.containsKey(name)) {
|
||||
List<String> values = headers.get(name).stream().filter(shouldWrite).toList();
|
||||
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
|
||||
headers.set(name, delimitedValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (value != null && shouldWrite.test(value)) {
|
||||
headers.set(name, value);
|
||||
}
|
||||
}
|
||||
@@ -303,11 +340,6 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
return HTTPS_SCHEME.equals(scheme) ? HTTPS_PORT : HTTP_PORT;
|
||||
}
|
||||
|
||||
private boolean hasHeader(ServerHttpRequest request, String name) {
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
return headers.containsKey(name) && StringUtils.hasLength(headers.getFirst(name));
|
||||
}
|
||||
|
||||
private String toHostHeader(ServerHttpRequest request) {
|
||||
int port = request.getURI().getPort();
|
||||
String host = request.getURI().getHost();
|
||||
|
||||
@@ -58,8 +58,10 @@ import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint
|
||||
import org.springframework.cloud.gateway.config.GatewayAutoConfigurationTests.CustomHttpClientFactory.CustomSslConfigurer;
|
||||
import org.springframework.cloud.gateway.config.HttpClientProperties.Pool.LeasingStrategy;
|
||||
import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.GRPCRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.GRPCResponseHeadersFilter;
|
||||
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
@@ -206,8 +208,8 @@ public class GatewayAutoConfigurationTests {
|
||||
"spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect",
|
||||
"spring.security.oauth2.client.registration[test].client-id=login-client")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class);
|
||||
assertThat(context).hasSingleBean(TokenRelayGatewayFilterFactory.class);
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class)
|
||||
.hasSingleBean(TokenRelayGatewayFilterFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,8 +227,8 @@ public class GatewayAutoConfigurationTests {
|
||||
"spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect",
|
||||
"spring.security.oauth2.client.registration[test].client-id=login-client")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class);
|
||||
assertThat(context).hasBean("myReactiveOAuth2AuthorizedClientManager");
|
||||
assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class)
|
||||
.hasBean("myReactiveOAuth2AuthorizedClientManager");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -290,8 +292,8 @@ public class GatewayAutoConfigurationTests {
|
||||
HttpClientCustomizedConfig.class, ServerPropertiesConfig.class))
|
||||
.withPropertyValues("server.http2.enabled=true")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(GRPCRequestHeadersFilter.class);
|
||||
assertThat(context).hasSingleBean(GRPCResponseHeadersFilter.class);
|
||||
assertThat(context).hasSingleBean(GRPCRequestHeadersFilter.class)
|
||||
.hasSingleBean(GRPCResponseHeadersFilter.class);
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
assertThat(httpClient.configuration().protocols()).contains(HttpProtocol.HTTP11, HttpProtocol.H2);
|
||||
});
|
||||
@@ -305,8 +307,8 @@ public class GatewayAutoConfigurationTests {
|
||||
HttpClientCustomizedConfig.class, ServerPropertiesConfig.class))
|
||||
.withPropertyValues("server.http2.enabled=false")
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(GRPCRequestHeadersFilter.class);
|
||||
assertThat(context).doesNotHaveBean(GRPCResponseHeadersFilter.class);
|
||||
assertThat(context).doesNotHaveBean(GRPCRequestHeadersFilter.class)
|
||||
.doesNotHaveBean(GRPCResponseHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,6 +339,32 @@ public class GatewayAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedHeaderFiltersNotEnabledByDefault() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
|
||||
ServerPropertiesConfig.class))
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(XForwardedHeadersFilter.class)
|
||||
.doesNotHaveBean(ForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedHeaderFiltersEnabledWithProperties() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
|
||||
ServerPropertiesConfig.class))
|
||||
.withPropertyValues("spring.cloud.gateway.forwarded.enabled=true",
|
||||
"spring.cloud.gateway.x-forwarded.enabled=true", "spring.cloud.gateway.trusted-proxies=.*")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(XForwardedHeadersFilter.class)
|
||||
.hasSingleBean(ForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ServerProperties.class)
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
|
||||
@@ -66,7 +66,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT,
|
||||
properties = { "spring.cloud.gateway.server.webflux.httpclient.connect-timeout=500",
|
||||
"spring.cloud.gateway.server.webflux.httpclient.response-timeout=2s",
|
||||
"logging.level.org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory=TRACE" })
|
||||
"logging.level.org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory=TRACE"
|
||||
"spring.cloud.gateway.server.webflux.trusted-proxies=.*", "spring.cloud.gateway.server.webflux.x-forwarded.enabled=true" })
|
||||
@DirtiesContext
|
||||
// default filter AddResponseHeader suppresses bug
|
||||
// https://github.com/spring-cloud/spring-cloud-gateway/issues/1315,
|
||||
|
||||
@@ -24,10 +24,17 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter.Forwarded;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
@@ -60,7 +67,7 @@ public class ForwardedHeadersFilterTests {
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -80,26 +87,37 @@ public class ForwardedHeadersFilterTests {
|
||||
public void forwardedHeaderExists() throws UnknownHostException {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get")
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https; for=23.45.67.89")
|
||||
.header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers.get(FORWARDED_HEADER)).hasSize(2);
|
||||
assertThat(headers.get(FORWARDED_HEADER)).hasSize(3);
|
||||
|
||||
List<Forwarded> forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER));
|
||||
|
||||
assertThat(forwardeds).hasSize(2);
|
||||
Forwarded addedForwardedHeader = forwardeds.get(0);
|
||||
Forwarded existingForwardedHeader = forwardeds.get(1);
|
||||
|
||||
assertThat(existingForwardedHeader.getValues()).containsEntry("proto", "http")
|
||||
.containsEntry("for", "\"10.0.0.1:80\"");
|
||||
|
||||
assertThat(addedForwardedHeader.getValues()).containsEntry("proto", "https")
|
||||
.containsEntry("for", "23.45.67.89");
|
||||
assertThat(forwardeds).hasSize(3);
|
||||
Optional<Forwarded> added = forwardeds.stream()
|
||||
.filter(forwarded -> forwarded.get("for").contains("10.0.0.1:80"))
|
||||
.findFirst();
|
||||
assertThat(added).isPresent();
|
||||
added.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", "\"10.0.0.1:80\"");
|
||||
});
|
||||
Optional<Forwarded> existing = forwardeds.stream()
|
||||
.filter(forwarded -> forwarded.get("for").equals("23.45.67.89"))
|
||||
.findFirst();
|
||||
assertThat(existing).isPresent();
|
||||
existing.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("for", "23.45.67.89");
|
||||
});
|
||||
existing = forwardeds.stream().filter(forwarded -> forwarded.get("for").equals("12.34.56.78")).findFirst();
|
||||
assertThat(existing).isPresent();
|
||||
existing.ifPresent(forwarded -> {
|
||||
assertThat(forwarded.getValues()).containsEntry("proto", "https").containsEntry("for", "12.34.56.78");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,7 +126,7 @@ public class ForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -129,7 +147,7 @@ public class ForwardedHeadersFilterTests {
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -149,7 +167,7 @@ public class ForwardedHeadersFilterTests {
|
||||
.remoteAddress(InetSocketAddress.createUnresolved("unresolvable-hostname", 80))
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter();
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -241,4 +259,83 @@ public class ForwardedHeadersFilterTests {
|
||||
Assertions.assertThat(forwarded.getValues()).containsEntry("by", "216.103.69.111");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionMatches() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
ReactiveWebServerFactoryAutoConfiguration.class, GatewayAutoConfiguration.class))
|
||||
.withPropertyValues("spring.cloud.gateway.trusted-proxies=11\\.0\\.0\\..*")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(ForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionDoesNotMatch() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
ReactiveWebServerFactoryAutoConfiguration.class, GatewayAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(ForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyTrustedProxiesFails() {
|
||||
Assertions.assertThatThrownBy(() -> new ForwardedHeadersFilter(""))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forwardedHeadersNotTrusted() throws Exception {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get")
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter("11\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).doesNotContainKeys(FORWARDED_HEADER);
|
||||
}
|
||||
|
||||
// verify that existing forwarded header is not forwarded if not trusted
|
||||
@Test
|
||||
public void untrustedForwardedForNotAppended() throws Exception {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get")
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(FORWARDED_HEADER, "proto=http;host=myhost;for=\"127.0.0.1:80\",for=10.0.0.11")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).containsKeys(FORWARDED_HEADER);
|
||||
List<String> forwardedHeaders = headers.get(FORWARDED_HEADER);
|
||||
Optional<String> filtered = forwardedHeaders.stream().filter(value -> value.contains("127.0.0.1")).findFirst();
|
||||
assertThat(filtered).isEmpty();
|
||||
filtered = forwardedHeaders.stream().filter(value -> value.contains("10.0.0.11")).findFirst();
|
||||
assertThat(filtered).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteAdddressIsNullUnTrustedProxyNotAppended() throws Exception {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(FORWARDED_HEADER, "proto=http;host=myhost;for=127.0.0.1")
|
||||
.build();
|
||||
|
||||
ForwardedHeadersFilter filter = new ForwardedHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).containsKeys(FORWARDED_HEADER);
|
||||
List<String> forwardedHeaders = headers.get(FORWARDED_HEADER);
|
||||
Optional<String> filtered = forwardedHeaders.stream().filter(value -> value.contains("127.0.0.1")).findFirst();
|
||||
assertThat(filtered).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,16 @@ import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
@@ -43,17 +51,20 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
|
||||
*/
|
||||
public class XForwardedHeadersFilterTests {
|
||||
|
||||
public static final String ALLOW_ALL_REGEX = ".*";
|
||||
|
||||
@Test
|
||||
public void remoteAddressIsNull() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).containsKeys(X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER);
|
||||
assertThat(headers).doesNotContainKeys(X_FORWARDED_FOR_HEADER)
|
||||
.containsKeys(X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost:8080");
|
||||
assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("8080");
|
||||
@@ -67,7 +78,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -87,7 +98,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -110,7 +121,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(X_FORWARDED_PROTO_HEADER, "https")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -134,7 +145,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(X_FORWARDED_PREFIX_HEADER, "/prefix")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setForAppend(false);
|
||||
filter.setHostAppend(false);
|
||||
filter.setPortAppend(false);
|
||||
@@ -159,7 +170,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setPrefixAppend(true);
|
||||
filter.setPrefixEnabled(true);
|
||||
|
||||
@@ -184,7 +195,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setPrefixAppend(true);
|
||||
filter.setPrefixEnabled(true);
|
||||
|
||||
@@ -210,7 +221,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setPrefixAppend(true);
|
||||
filter.setPrefixEnabled(true);
|
||||
|
||||
@@ -232,7 +243,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setPrefixAppend(true);
|
||||
filter.setPrefixEnabled(true);
|
||||
filter.setForEnabled(false);
|
||||
@@ -258,7 +269,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setPrefixAppend(true);
|
||||
filter.setPrefixEnabled(true);
|
||||
filter.setForEnabled(false);
|
||||
@@ -284,7 +295,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
filter.setForEnabled(false);
|
||||
filter.setHostEnabled(false);
|
||||
filter.setPortEnabled(false);
|
||||
@@ -303,7 +314,7 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(X_FORWARDED_FOR_HEADER, "10.0.0.1")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
@@ -319,11 +330,92 @@ public class XForwardedHeadersFilterTests {
|
||||
.header(X_FORWARDED_FOR_HEADER, "10.0.0.1")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter();
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter(ALLOW_ALL_REGEX);
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).doesNotContainKeys(X_FORWARDED_PROTO_HEADER, X_FORWARDED_HOST_HEADER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionMatches() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
ReactiveWebServerFactoryAutoConfiguration.class, GatewayAutoConfiguration.class))
|
||||
.withPropertyValues(GatewayProperties.PREFIX + ".trusted-proxies=11\\.0\\.0\\..*")
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(XForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trustedProxiesConditionDoesNotMatch() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, SslAutoConfiguration.class,
|
||||
ReactiveWebServerFactoryAutoConfiguration.class, GatewayAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(XForwardedHeadersFilter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyTrustedProxiesFails() {
|
||||
Assertions.assertThatThrownBy(() -> new XForwardedHeadersFilter(""))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xForwardedHeadersNotTrusted() throws Exception {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter("11\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).doesNotContainKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
}
|
||||
|
||||
// : verify that existing x-forwarded-* headers are not forwarded
|
||||
// if x-forwarded-for is not trusted
|
||||
@Test
|
||||
public void untrustedXForwardedForNotAppended() throws Exception {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80))
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(X_FORWARDED_FOR_HEADER, "127.0.0.1")
|
||||
.header(X_FORWARDED_FOR_HEADER, "10.0.0.10")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).doesNotContain("127.0.0.1")
|
||||
.contains("10.0.0.1", "10.0.0.10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteAdddressIsNullUnTrustedProxyNotAppended() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
|
||||
.header(HttpHeaders.HOST, "myhost")
|
||||
.header(X_FORWARDED_FOR_HEADER, "127.0.0.1")
|
||||
.build();
|
||||
|
||||
XForwardedHeadersFilter filter = new XForwardedHeadersFilter("10\\.0\\.0\\..*");
|
||||
|
||||
HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request));
|
||||
|
||||
assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER,
|
||||
X_FORWARDED_PROTO_HEADER);
|
||||
|
||||
assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).doesNotContain("127.0.0.1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
import static org.springframework.cloud.gateway.test.TestUtils.getMap;
|
||||
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT,
|
||||
properties = "spring.cloud.gateway.server.webflux.forwarded.by.enabled=true")
|
||||
properties = { "spring.cloud.gateway.server.webflux.forwarded.by.enabled=true", "spring.cloud.gateway.server.webflux.forwarded.enabled=true", "spring.cloud.gateway.server.webflux.x-forwarded.enabled=true",
|
||||
"spring.cloud.gateway.server.webflux.trusted-proxies=.*",
|
||||
"logging.level.org.springframework.cloud.gateway.filter.headers=TRACE" })
|
||||
@DirtiesContext
|
||||
@SuppressWarnings("unchecked")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
|
||||
@@ -22,3 +22,4 @@ spring:
|
||||
filters:
|
||||
- SetPath=/httpbin/
|
||||
- SetStatus=200
|
||||
trusted-proxies: .*
|
||||
|
||||
Reference in New Issue
Block a user