簡介
在Spring Cloud Gateway作為服務網關的情況下,防止後端服務的重定向行為直接傳遞到客戶端,通常需要在Gateway層面處理響應的3xx狀態碼,特別是針對重定向(如301、302)的情況。可以通過編寫自定義全局過濾器來實現這一目的:
方式一
創建自定義過濾器
創建自定義過濾器HandleBackendRedirectGatewayFilter.
package org.fiend.a.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
/**
* spring cloud gateway 準發訪問後端後,後端服務重定向,返回數據時,
* 將後端服務重定向後的重定向為本服務 重定向的path地址
* @author Administrator 2024-01-30 17:01:43
*/
public class HandleBackendRedirectGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
HttpStatus status = exchange.getResponse().getStatusCode();
// 檢查響應狀態碼是否為重定向
int statusCode = null != status ? status.value() : 0;
if (statusCode >= 300 && statusCode < 400) {
// 如果是重定向,可以修改響應狀態碼和頭部信息,避免客戶端跟隨重定向
// exchange.getResponse().setStatusCode(HttpStatus.OK);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders responseHeaders = response.getHeaders();
// 構造新的Location
URI requestURI = request.getURI();
URI responseURI = responseHeaders.getLocation(); // Location示例: http://localhost:8700/printCookie
String redirectPath = null != responseURI ? responseURI.getPath() : "/"; // 示例: /redirect
String newLocation = "http://" requestURI.getHost() ":" requestURI.getPort() redirectPath;
MultiValueMap<String, String> queryParams = request.getQueryParams();
if (!queryParams.isEmpty()) {
StringBuilder sb = new StringBuilder(newLocation);
sb.append("?");
queryParams.forEach((key, values) -> values.forEach(value -> {
sb.append(key).append("=").append(value).append("&");
}));
// 去掉最後一個&
newLocation = sb.deleteCharAt(sb.length() - 1).toString();
}
exchange.getResponse().getHeaders().setLocation(URI.create(newLocation));
}
}));
}
// @Override
// public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// ServerHttpResponse response = exchange.getResponse();
// HttpStatus status = exchange.getResponse().getStatusCode();
// // 檢查響應狀態碼是否為重定向
// int statusCode = null != status ? status.value() : 0;
// if (statusCode >= 300 && statusCode < 400) {
// // 如果是重定向,可以修改響應狀態碼和頭部信息,避免客戶端跟隨重定向
// // exchange.getResponse().setStatusCode(HttpStatus.OK);
// // exchange.getResponse().getHeaders().remove(HttpHeaders.LOCATION);
//
// // =========================== 方式二 =========================== //
// HttpHeaders responseHeaders = response.getHeaders();
// // URI location2 = responseHeaders.getLocation(); // 示例: http://localhost:8700/printCookie
//
// ServerHttpRequest request = exchange.getRequest();
//
// // 從原始請求中獲取location, path和query參數
// String originalPath1 = request.getPath().value(); // 示例: /redirect
//
// // URI requestLocation = request.getHeaders().getLocation();
// // String gatewayHost = null == requestLocation ? "localhost" : requestLocation.getHost(); // 示例: localhost
//
// // 構造新的Location
// URI requestURI = request.getURI();
// URI responseURI = responseHeaders.getLocation();
// String redirectPath = null != responseURI ? responseURI.getPath() : "/"; // 示例: /redirect
// // String newLocation = "http://" requestURI.getHost() ":" requestURI.getPort() redirectPath;
// String newLocation = "redirect:" redirectPath;
//
// MultiValueMap<String, String> queryParams = request.getQueryParams();
// if (!queryParams.isEmpty()) {
// StringBuilder sb = new StringBuilder(newLocation);
// sb.append("?");
// queryParams.forEach((key, values) -> values.forEach(value -> {
// sb.append(key).append("=").append(value).append("&");
// }));
// // 去掉最後一個&
// newLocation = sb.deleteCharAt(sb.length() - 1).toString();
// }
//
// // ============================ 方式三 ========================== //
// // headers = new HttpHeaders();
// // // 從exchange獲取原始請求路徑並添加到gateway地址前
// // String originalPath = exchange.getRequest().getURI().getPath();
// // // String location = "https://your-gateway-host/" originalPath;
// // String location = requestURI.getHost() ":" requestURI.getPort() originalPath;
// // headers.setLocation(URI.create(location));
// // exchange.getResponse().getHeaders().setLocation(URI.create(location));
// }
// }));
// }
// @Override
// public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// ServerHttpResponse response = exchange.getResponse();
// HttpStatus status = response.getStatusCode();
//
// // 檢查響應狀態碼是否為重定向
// // int statusCode = null != status ? status.value() : 0;
// if ((null != status) && status.is3xxRedirection()) {
// // 獲取重定向URL
// // URI location = response.getHeaders().getLocation();
//
// ServerHttpRequest request = exchange.getRequest();
// HttpHeaders responseHeaders = response.getHeaders();
// URI requestURI = request.getURI();
// URI responseURI = responseHeaders.getLocation();
// if (!requestURI.getHost().equals(null == responseURI ? "" : responseURI.getHost()) || (requestURI.getPort() != (null == responseURI ? -1 : responseURI.getPort()))) {
// // 重新設置請求路徑
// String redirectPath = null != responseURI ? responseURI.getPath() : "/"; // 示例: /redirect
// String newLocation = "http://" requestURI.getHost() ":" requestURI.getPort() redirectPath;
//
// MultiValueMap<String, String> queryParams = request.getQueryParams();
// if (!queryParams.isEmpty()) {
// StringBuilder sb = new StringBuilder(newLocation);
// sb.append("?");
// queryParams.forEach((key, values) -> values.forEach(value -> {
// sb.append(key).append("=").append(value).append("&");
// }));
// // 去掉最後一個&
// newLocation = sb.deleteCharAt(sb.length() - 1).toString();
// }
// URI newLocationURI = URI.create(newLocation);
// exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, exchange.getRequest().mutate().uri(newLocationURI).build());
// }
// }
//
// chain.filter(exchange);
// }));
//
// // ServerHttpResponse response = exchange.getResponse();
// // HttpStatus status = response.getStatusCode();
// // if ((null != status) && status.is3xxRedirection()) {
// // // 獲取重定向URL
// // // URI location = response.getHeaders().getLocation();
// // // 重新設置請求路徑
// // exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, exchange.getRequest().mutate().uri(getRedirectURL(exchange)).build());
// // }
// // return chain.filter(exchange);
// }
private URI getRedirectURL(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders responseHeaders = response.getHeaders();
// 構造新的Location
URI requestURI = request.getURI();
URI responseURI = responseHeaders.getLocation();
String redirectPath = null != responseURI ? responseURI.getPath() : "/"; // 示例: /redirect
String newLocation = requestURI.getHost() ":" requestURI.getPort() redirectPath;
MultiValueMap<String, String> queryParams = request.getQueryParams();
if (!queryParams.isEmpty()) {
StringBuilder sb = new StringBuilder(newLocation);
sb.append("?");
queryParams.forEach((key, values) -> values.forEach(value -> {
sb.append(key).append("=").append(value).append("&");
}));
// 去掉最後一個&
newLocation = sb.deleteCharAt(sb.length() - 1).toString();
}
return URI.create(newLocation);
}
@Override
public int getOrder() {
// 設置過濾器執行順序,數值越小優先級越高
return 0;
}
}
在上述代碼中,自定義的DisableBackendRedirectFilter會在請求生命周期結束後檢查響應的狀態碼。如果發現是重定向,它會移除Location頭或者將狀態碼改為非重定向狀態,從而阻止客戶端跟隨重定向。
創建自定義過濾器工廠
package org.fiend.a.gateway.config;
import org.fiend.a.gateway.filter.HandleBackendRedirectGatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
/**
* @author Administrator 2024-01-30 17:32:02
*/
@Component
public class HandleBackendRedirectGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
@Override
public GatewayFilter apply(Object config)
{
return new HandleBackendRedirectGatewayFilter();
}
}
配置application.yml
配置方式一
spring:
cloud:
gateway:
routes:
# 防止重定向
- id: handle-redirect-service
# 瀏覽器請求 http://localhost:8510/redirect 時,
# 由於該路徑轉發訪問的http://localhost:8700/redirect是一個重定向請求,
# 因此, 會重定向到 http://localhost:8700/printCookie 路徑
# 這裡配置 DisableBackendRedirectFilter 過濾器, 防止出現這種情況
uri: http://localhost:8700
predicates:
- Path=/redirect/**
filters:
- HandleBackendRedirect # 添加自定義過濾器工程名稱
配置方式二
spring:
cloud:
gateway:
routes:
# 防止重定向
- id: handle-redirect-service
# 瀏覽器請求 http://localhost:8510/redirect 時,
# 由於該路徑轉發訪問的http://localhost:8700/redirect是一個重定向請求,
# 因此, 會重定向到 http://localhost:8700/printCookie 路徑
# 這裡配置 DisableBackendRedirectFilter 過濾器, 防止出現這種情況
uri: http://localhost:8700
predicates:
- Path=/redirect/**
filters:
- name: HandleBackendRedirect # 添加自定義過濾器工程名稱
方式二
通過編程方式註冊到Spring容器:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("your_route_definition", r -> r.path("/path_to_protect")
.filters(f -> f.filter(new HandleBackendRedirectGatewayFilter()))
.uri("lb://backend-service"))
.build();
}
請註意,根據實際需求,你可能還需要更精細地控制哪些路由應該禁用重定向行為。