Custom Predicate

Train of thought

In the SCG initial resolution we’ve seen how Predicate is assembled according to our configuration. RemoteAddrRoutePredicateFactory, for example.

public class RemoteAddrRoutePredicateFactory
		extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> 
Copy the code

RemoteAddrRoutePredicateFactory inherited abstract class AbstractRoutePredicateFactory, generic class for internal Config.

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

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

I rewrote the shortcutType and shortcutFieldOrder methods. These two methods are mainly used to define the configuration and generation mode of Config. The details are not detailed.

@Override public Predicate<ServerWebExchange> apply(Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { return false; }}; }Copy the code

Implement the Apply method and internally create the GatewayPredicate anonymous inner class.

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; }}Copy the code

Define the inner class Config according to the Predicate function. To summarize, there are a few things that Predicate does:

  1. Class name, starting with XXX and ending with RoutePredicateFactory.
  2. Defines the internal Config class, which internally defines the configuration required for the Predicate.
  3. Inherits abstract classesAbstractRoutePredicateFactory, generics are inner classesConfig
  4. rewriteshortcutTypeandshortcutFieldOrdermethods
  5. implementationapplyMethod, created internallyGatewayPredicateAnonymous inner classes.

Customize blacklist Predicate

implementation

The blacklist can be restricted by the IP address or IP address segment. When a request is received, the system obtains the IP address of the current client and checks whether the IP address matches the configured blacklist. If the IP address matches the configured blacklist, the system returns false. Concrete implementation logic below the code, similar to SCG built-in RemoteAddrRoutePredicateFactory

/** * Description: Blacklist 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;
	}

	/** * This method needs to be overridden to create Config using * @return */
	@Override
	public ShortcutType shortcutType() {

		/** * GATHER_LIST Type can have only one shortcutField * {@link this#shortcutFieldOrder()} */
		return GATHER_LIST;
	}

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


	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		/** * IpSubnetFilterRule is an IP address filtering rule defined in Netty */
		// Generate rules based on configured sourcesList<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) {/ / as long as you comply with any rules returns false, in contrast to the RemoteAddrRoutePredicateFactoryfor (IpSubnetFilterRule source : sources) { if (source.matches(remoteAddress)) { return false; }}}// If no rules match, pass
				return true; }}; } private void addSource(List<IpSubnetFilterRule> sources, String source) {// Check whether the IP address segment is configured. If no, the maximum value is 32 by default. If 172.15.32.15 is configured, the value is changed to 172.15.32.15/32if (! source.contains("/")) { // no netmask, add default
			source = source + 32 "/";
		}
		// Assume that 172.15.32.15/18 is configured
		// Split according to '/' [0]:172.15.32.15 [1]:18
		String[] ipAddressCidrPrefix = source.split("/".2);
		String ipAddress = ipAddressCidrPrefix[0];
		int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);

		// Set the rejection rule
		sources.add(
				new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.REJECT));
	}

	public static class Config{
		/** * Multiple IP addresses /IP segment */ can be configured
		@NotEmpty
		private List<String> sources = new ArrayList<>();
		/** * is used to resolve client IP address */@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; }}}Copy the code

use

spring:
  cloud:
    gateway:
      routes:
      - id: hello_route
        uri: http://localhost:8088/api/hello
        predicates:
        - BlackRemoteAddr=169.254183.1./18.172.1731.1./18-path =/ API /helloCopy the code

validation

Start the SCG and a local service and expose the interface as/API/Hello, returning hello World. In customBlackRemoteAddrRoutePredicateFactory#testThe breakpoints.



** Environment: local IP ** 169.254.183.16. Set the limit IP address to169.254.183.1/18. At this point, our request should be rejected with a 404 status code.



You can seePredicateGot our IP and matched our configured rules, so return false to indicate that the route did not match, so return 404.



Change the configuration to another IP address segment, for example, 169.254.183.18/32. In this case, the IP address does not comply with the rule and is allowed.

restartThe project is tested again.



It can be seen that our IP address does not match the configured rule. Return true, indicating that the route can be used.



I highlighted the “restart” above, each change of the rule must restart the project to take effect, so in actual use, we must realize that we can dynamically modify our rules without restarting, so how to do?

Refreshing the Predicate configuration can be done by writing the configuration to an external source, such as the DB, and periodically sending an event like Nacos to refresh the configuration. Tomorrow we will implement dynamic refresh configuration based on Redis.