Spring Cloud Gateway發現後臺重定向時,再重定向到本服務

2024年2月6日 27点热度 0人点赞

簡介

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();
}

請註意,根據實際需求,你可能還需要更精細地控制哪些路由應該禁用重定向行為。