天天看點

Spring Cloud Gateway源碼解析-10-自定義Predicate實作黑名單自定義Predicate

自定義Predicate

思路

在SCG初始化解析中我們已經知道了Predicate是怎麼根據我們的配置裝配的。

RemoteAddrRoutePredicateFactory

為例。

public class RemoteAddrRoutePredicateFactory
		extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> 
           

RemoteAddrRoutePredicateFactory

繼承了抽象類

AbstractRoutePredicateFactory

,泛型為内部類

Config

@Override
	public ShortcutType shortcutType() {
		return GATHER_LIST;
	}

	@Override
	public List<String> shortcutFieldOrder() {
		return Collections.singletonList("sources");
	}
           

重寫

shortcutType

shortcutFieldOrder

方法,這兩個方法主要是用來定義Config的配置及生成方式,具體細節不再深叙,個人認為SCG的

ShortcutType

設計的很不好了解。

@Override
public Predicate<ServerWebExchange> apply(Config config) {

	return new GatewayPredicate() {
		@Override
		public boolean test(ServerWebExchange exchange) {
			return false;
		}
	};
}
           

實作

apply

方法,内部建立

GatewayPredicate

匿名内部類。

public static class Config {

		@NotEmpty
		private List<String> sources = new ArrayList<>();

		@NotNull
		private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
		};

		public List<String> getSources() {
			return sources;
		}

		public Config setSources(List<String> sources) {
			this.sources = sources;
			return this;
		}

		public Config setSources(String... sources) {
			this.sources = Arrays.asList(sources);
			return this;
		}

		public Config setRemoteAddressResolver(
				RemoteAddressResolver remoteAddressResolver) {
			this.remoteAddressResolver = remoteAddressResolver;
			return this;
		}

	}
           

根據Predicate功能定義内部類Config。

綜上所述總結自定義Predicate要做的事情有如下幾點:

  1. 類名稱,以XXX開頭,RoutePredicateFactory結尾。
  2. 定義内部Config類,内部定義Predicate所需配置。
  3. 繼承了抽象類

    AbstractRoutePredicateFactory

    ,泛型為内部類

    Config

  4. 重寫

    shortcutType

    shortcutFieldOrder

    方法
  5. 實作

    apply

    方法,内部建立

    GatewayPredicate

    匿名内部類。

自定義黑名單Predicate

實作

實作黑名單可以通過配置的IP或者IP段進行限制。當請求進入時,擷取到目前請求的用戶端的IP,判斷是否與配置的黑名單比對,比對傳回false即可。

具體的實作邏輯見下方代碼即可,與SCG内置的

RemoteAddrRoutePredicateFactory

類似

/**
 * Description:黑名單Predicate
 * @author li.hongjian
 * @email [email protected]
 * @Date 2021/3/31
 */
public class BlackRemoteAddrRoutePredicateFactory
		extends AbstractRoutePredicateFactory<BlackRemoteAddrRoutePredicateFactory.Config> {

	public BlackRemoteAddrRoutePredicateFactory() {
		super(Config.class);
	}

	@NotNull
	private List<IpSubnetFilterRule> convert(List<String> values) {
		List<IpSubnetFilterRule> sources = new ArrayList<>();
		for (String arg : values) {
			addSource(sources, arg);
		}
		return sources;
	}

	/**
	 * 此方法需重寫,用來建立Config使用
	 * @return
	 */
	@Override
	public ShortcutType shortcutType() {

		/**
		 * GATHER_LIST類型隻能有一個shortcutField
		 * {@link this#shortcutFieldOrder()}
		 */
		return GATHER_LIST;
	}

	/**
	 * 配置中的value對應的字段
	 * 比如目前我們的Config中的字段就為sources
	 * @return
	 */
	@Override
	public List<String> shortcutFieldOrder() {
		return Collections.singletonList("sources");
	}


	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		/**
		 * IpSubnetFilterRule是Netty中定義的IP過濾規則
		 */
		//根據配置的sources生成對應規則
		List<IpSubnetFilterRule> sources = convert(config.sources);

		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
				InetSocketAddress remoteAddress = config.remoteAddressResolver
						.resolve(exchange);
				if (remoteAddress != null && remoteAddress.getAddress() != null) {
					//隻要符合任意一個規則就傳回false,與RemoteAddrRoutePredicateFactory相反
					for (IpSubnetFilterRule source : sources) {
						if (source.matches(remoteAddress)) {
							return false;
						}
					}
				}
				//如果沒有比對所有規則,則通過
				return true;
			}
		};
	}

	private void addSource(List<IpSubnetFilterRule> sources, String source) {
		//判斷是否配置了IP段,如果沒有則預設為最大為32,如配置172.15.32.15,則被修改為172.15.32.15/32
		if (!source.contains("/")) { // no netmask, add default
			source = source + "/32";
		}
		//假設配置的為 172.15.32.15/18
		//根據'/'分割  [0]:172.15.32.15  [1]:18
		String[] ipAddressCidrPrefix = source.split("/", 2);
		String ipAddress = ipAddressCidrPrefix[0];
		int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);

		//設定拒絕規則
		sources.add(
				new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.REJECT));
	}

	public static class Config{
		/**
		 * 可配置多個IP/IP段
		 */
		@NotEmpty
		private List<String> sources = new ArrayList<>();
		/**
		 * 用來解析用戶端IP
		 */
		@NotNull
		private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
		};

		public List<String> getSources() {
			return sources;
		}

		public Config setSources(List<String> sources) {
			this.sources = sources;
			return this;
		}

		public Config setSources(String... sources) {
			this.sources = Arrays.asList(sources);
			return this;
		}

		public Config setRemoteAddressResolver(
				RemoteAddressResolver remoteAddressResolver) {
			this.remoteAddressResolver = remoteAddressResolver;
			return this;
		}
	}
}
           

使用

spring:
  cloud:
    gateway:
      routes:
      - id: hello_route
        uri: http://localhost:8088/api/hello
        predicates:
        - BlackRemoteAddr=169.254.183.1/18,172.17.31.1/18 #配置指定IP段限制
        - Path=/api/hello
           

驗證

啟動SCG和一個本地服務并暴露接口為/api/hello,傳回結果為hello world。在自定義的

BlackRemoteAddrRoutePredicateFactory#test

中斷點.

**環境:本機IP **169.254.183.16。配置限制IP為

169.254.183.1/18

。此時我們的請求應該是會被拒絕傳回404狀态碼的。

Spring Cloud Gateway源碼解析-10-自定義Predicate實作黑名單自定義Predicate

可以看到

Predicate

擷取到了我們的IP,并且比對了我們配置的規則,是以傳回false,表示不比對該路由,是以傳回404.

Spring Cloud Gateway源碼解析-10-自定義Predicate實作黑名單自定義Predicate

修改配置為其他IP段,比如169.254.183.18/32,此時我們的IP是不符合該規則的,是以會放行。

重新開機項目再次測試。

Spring Cloud Gateway源碼解析-10-自定義Predicate實作黑名單自定義Predicate

可以看到我們的IP并沒有比對配置的規則,傳回true,表示可以走該路由。

Spring Cloud Gateway源碼解析-10-自定義Predicate實作黑名單自定義Predicate

上邊我對“重新開機”重點标注了,每次修改規則都要重新開機項目才能生效,那麼在實際用的時候,我們肯定想實作不重新開機就可以動态修改我們配置的規則,那麼該怎麼做呢?

我們可以通過将配置寫到外部一個源中,比如DB中,通過類似Nacos一樣定時發送一個重新整理配置的事件去實作重新整理Predicate配置。明天我們就來基于Redis來實作動态重新整理配置的功能。