【第14章】Spring Cloud之Gateway路由断言(IP黑名单)

文章目录


前言

Spring Cloud Gateway可以让我们根据请求内容精确匹配到对应路由服务,官方已经内置了很多路由断言,我们也可以根据需求自己定义,RemoteAddrRoutePredicateFactory就像是根据IP去匹配的白名单,接下来我们根据它来自定义一个IP黑名单。


一、内置路由断言

1. 案例(Weight)

yml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

这条路线将把约80%的流量转发到weighthigh.org,约20%的流量转发给weighlow.org

2. 更多断言

序号 断言类型 用法 描述
1 After - After=2017-01-20T17:42:47.789-07:00[America/Denver] 此路由与2017年1月20日17:42:47之后的任何请求相匹配。
2 Before - Before=2017-01-20T17:42:47.789-07:00[America/Denver] 此路由与2017年1月20日17:42:47之前的任何请求相匹配。
3 Between - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] 此路由与2017年1月20日17:42:47之后至2017年1月21日17:42:47之前提出的任何请求相匹配。
4 Cookie - Cookie=chocolate, ch.p 此路由匹配名为chocolate的cookie的请求,该cookie的值与ch.p匹配。
5 Header - Header=X-Request-Id, \d+ 此路由匹配请求有一个名为X-request-Id的请求头,其值与\d+正则表达式匹配(即它有一个或多个数字的值)。
6 Host - Host=.somehost.org,.anotherhost.org 此路由匹配请求的Host值为www.somehost.orgbeta.somehost.org或www.anotherhost.org
7 Method - Method=GET,POST 此路由匹配请求方法是GET或POST。
8 Path - Path=/red/{segment},/blue/{segment} 此路由匹配请求路径为:/red/1 或 /red/1/ 或 /red/blue 或/blue/green。
9 Query - Query=green - Query=green - Query=red, gree.
10 RemoteAddr - RemoteAddr=192.168.1.1/24 如果请求的远程地址是192.168.1.10,则此路由匹配。
11 XForwarded Remote Addr - XForwardedRemoteAddr=192.168.1.1/24 如果X-Forwarded-For标头包含例如192.168.1.10,则此路由匹配。

二、自定义路由断言

1. 黑名单断言

java 复制代码
package org.example.gateway.predicate;

import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.ipfilter.IpSubnetFilterRule;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import static org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.GATHER_LIST;

/**
 * Create by zjg on 2024/7/29
 */
@Component
public class BlackRemoteAddrRoutePredicateFactory  extends AbstractRoutePredicateFactory<BlackRemoteAddrRoutePredicateFactory.Config> {
    private static final Log log = LogFactory.getLog(BlackRemoteAddrRoutePredicateFactory.class);

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

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

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

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

    @Override
    public Predicate<ServerWebExchange> apply(BlackRemoteAddrRoutePredicateFactory.Config config) {
        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) {
                    String hostAddress = remoteAddress.getAddress().getHostAddress();
                    String host = exchange.getRequest().getURI().getHost();

                    if (log.isDebugEnabled() && !hostAddress.equals(host)) {
                        log.debug("Black remote addresses didn't match " + hostAddress + " != " + host);
                    }

                    for (IpSubnetFilterRule source : sources) {
                        if (source.matches(remoteAddress)) {
                            exchange.getAttributes().put("BlackRemoteAddrRoutePredicateFactory",remoteAddress.getAddress().getHostAddress());
                            return false;//能匹配到则在黑名单中,不再执行
                        }
                    }
                }

                return true;
            }

            @Override
            public Object getConfig() {
                return config;
            }

            @Override
            public String toString() {
                return String.format("BlackRemoteAddrs: %s", config.getSources());
            }
        };
    }

    private void addSource(List<IpSubnetFilterRule> sources, String source) {
        if (!source.contains("/")) { // no netmask, add default
            source = source + "/32";
        }

        String[] ipAddressCidrPrefix = source.split("/", 2);
        String ipAddress = ipAddressCidrPrefix[0];
        int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);

        sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT));
    }

    @Validated
    public static class Config {

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

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

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

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

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

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

    }
}

2. 全局异常处理

java 复制代码
package org.example.gateway.config;

import org.example.common.model.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.reactive.resource.NoResourceFoundException;
import org.springframework.web.server.ServerWebExchange;
import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * Create by zjg on 2024/7/29
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    @ExceptionHandler(NoResourceFoundException.class)//无可用路由
    public Result exception(ServerWebExchange exchange, NoResourceFoundException ex){
        String detail = ex.getBody().getDetail();
        String mark="resource ";
        String message = detail.substring(detail.indexOf(mark) + mark.length());
        setStatusCode(exchange.getResponse(),ex.getStatusCode());
        if(StringUtils.hasText(exchange.getAttribute("BlackRemoteAddrRoutePredicateFactory"))){//IP黑名单
            return  Result.error(ex.getStatusCode().value(),"拒绝访问","您的IP已被添加到黑名单中,拒绝访问!");
        }
        return  Result.error(ex.getStatusCode().value(),"无可用路由",String.format("没有可用的路由[%s]",message));
    }
    @ExceptionHandler(NotFoundException.class)//无可用服务
    public Result exception(ServerHttpResponse response,NotFoundException ex){
        logger.error(ex.getMessage());
        String detail = ex.getBody().getDetail();
        String mark="for ";
        String message = detail.substring(detail.indexOf(mark) + mark.length());
        setStatusCode(response,ex.getStatusCode());
        return  Result.error(ex.getStatusCode().value(),"服务不可用",String.format("没有可用的服务实例[%s]",message));
    }
    @ExceptionHandler(Exception.class)//异常保底
    public Result exception(ServerHttpResponse response,Exception exception){
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer=new PrintWriter(stringWriter);
        exception.printStackTrace(writer);
        logger.error(stringWriter.toString());
        setStatusCode(response,HttpStatus.INTERNAL_SERVER_ERROR);
        return  Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());
    }
    private void setStatusCode(ServerHttpResponse response,HttpStatusCode httpStatusCode){
        response.setStatusCode(httpStatusCode);
    }
}

3. 应用配置

yml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: provider-service
        uri: lb://provider-service
        predicates:
        - Path=/provider/**
        - BlackRemoteAddr=192.168.1.1/24,127.0.0.1

4. 单元测试

bash 复制代码
curl 192.168.0.104:8888/provider/hello

正常访问

黑名单访问


总结

回到顶部

这样我们就能通过断言配置黑名单,可以针对固定IP做灵活处理。

相关推荐
Yaml437 分钟前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~38 分钟前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong16168840 分钟前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7891 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java2 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~2 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果2 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
小屁孩大帅-杨一凡3 小时前
java后端请求想接收多个对象入参的数据
java·开发语言
java1234_小锋3 小时前
使用 RabbitMQ 有什么好处?
java·开发语言
TangKenny3 小时前
计算网络信号
java·算法·华为