欢迎来到徐庆高(Tea)的个人博客网站
磨难很爱我,一度将我连根拔起。从惊慌失措到心力交瘁,我孤身一人,但并不孤独无依。依赖那些依赖我的人,信任那些信任我的人,帮助那些给予我帮助的人。如果我愿意,可以分裂成无数面镜子,让他们看见我,就像看见自己。察言观色和模仿学习是我的领域。像每个深受创伤的人那样,最终,我学会了随遇而安。
当前位置: 日志文章 > 详细内容

Zuul实现动态路由与权限过滤器方式

2025年07月17日 Java
前言介绍在实际的业务开发中不只是将路由配置放到文件中,而是需要进行动态管理并且可以在变化时不用重启系统就可以更新。与此同时还需要在接口访问的时候,可以增加一些权限验证以防止恶意访问。1.filter过

前言介绍

在实际的业务开发中不只是将路由配置放到文件中,而是需要进行动态管理并且可以在变化时不用重启系统就可以更新。与此同时还需要在接口访问的时候,可以增加一些权限验证以防止恶意访问。

1.filter过滤器,通过继承实现对应方法可以进行控制过滤;

  • pre:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • routing:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 apache httpclient 或 netfilx ribbon 请求微服务。
  • post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 http header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • error:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,zuul 还允许我们创建自定义的过滤器类型。例如,我们可以定制一种 static 类型的过滤器,直接在 zuul 中生成响应,而不将请求转发到后端的微服务。

2.自定义路由,同构实现simpleroutelocator和refreshableroutelocator自动刷新

  • protected map<string, zuulroute> locateroutes():此方法是加载路由配置的,父类中是获取properties中的路由配置,可以通过扩展此方法,达到动态获取配置的目的
  • public route getmatchingroute(string path):此方法是根据访问路径,获取匹配的路由配置,父类中已经匹配到路由,可以通过路由id查找自定义配置的路由规则,以达到根据自定义规则动态分流的效果

环境准备

  • 1.jdk 1.8、idea2018、maven3
  • 2.spring boot 2.0.6.release
  • 3.spring cloud finchley.sr2

代码示例

itstack-demo-springcloud-08
├── itstack-demo-springcloud-eureka-client
│   └── src
│       └── main
│           ├── java
│           │   └── org.itstack.demo
│           │        ├── web
│           │        │   └── eurekaclientcontroller.java
│           │        └── eurekaclientapplication.java
│           └── resources   
│               └── application.yml
├── itstack-demo-springcloud-eureka-server
│   └── src
│       └── main
│           ├── java
│           │   └── org.itstack.demo
│           │        └── eurekaserverapplication.java
│           └── resources   
│               └── application.yml
├── itstack-demo-springcloud-hystrix-feign
│   └── src
│       └── main
│           ├── java
│           │   └── org.itstack.demo
│           │        ├── service
│           │        │   ├── hystrix
│           │        │   │   └── feignservicehystrix.java
│           │        │   └── feignservice.java
│           │        ├── web
│           │        │   └── feigncontroller.java
│           │        └── feignapplication.java
│           └── resources   
│               └── application.yml
├── itstack-demo-springcloud-hystrix-ribbon
│   └── src
│       └── main
│           ├── java
│           │   └── org.itstack.demo
│           │        ├── service
│           │        │   └── ribbonservice.java
│           │        ├── web
│           │        │   └── ribboncontroller.java      
│           │        └── ribbonapplication.java
│           └── resources   
│               └── application.yml
└── itstack-demo-springcloud-zuul
    └── src
        └── main
            ├── java
            │   └── org.itstack.demo   
            │        ├── config
            │        │   └── zuulconfig.java
            │        ├── filter
            │        │   └── tokenfilter.java
            │        ├── router
            │        │   └── routelocator.java
            │        ├── service
            │        │   └── refreshrouteservice.java
            │        └── zuulapplication.java
            └── resources   
                └── application.yml

itstack-demo-springcloud-zuul & 动态路由与权限过滤

  • 1.通过routelocator实现自己的动态路由配置,其实就是把配置文件内容转移到这里用代码类实现,并且可以根据需要修改为从数据库里获取。
  • 2.tokenfilter提供了权限验证功能,当用户访问时候会带上token否则拦截
  • 3.此外还提供了自动刷新的接口,用于外部调用刷新配置
  • 4.最后我们需要修改application配置,zuul中还需要排除不做路由的接口[刷新权限接口]

config/zuulconfig.java & 路由配置类

@configuration
public class zuulconfig {

    @autowired
    private zuulproperties zuulproperties;
    @autowired
    private serverproperties server;

    @bean
    public routelocator routelocator() {
        return new routelocator(this.server.getservlet().getpath(), this.zuulproperties);
    }

}

filter/tokenfilter.java & 权限校验类

public class tokenfilter extends zuulfilter {

    /**
     * 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
     * filterconstants.pre_type:代表会在请求被路由之前执行。
     * pre、routing、post、error
     */
    public string filtertype() {
        return filterconstants.pre_type;
    }

    /**
     * filter执行顺序,通过数字指定。[数字越大,优先级越低]
     */
    public int filterorder() {
        return 0;
    }

    /**
     * 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。
     * 实际运用中我们可以利用该函数来指定过滤器的有效范围。
     */
    public boolean shouldfilter() {
        return true;
    }

    /*
     * 具体执行逻辑
     */
    public object run() {
        requestcontext ctx = requestcontext.getcurrentcontext();
        httpservletrequest request = ctx.getrequest();
        string token = request.getparameter("token");
        if (token == null || token.isempty()) {
            ctx.setsendzuulresponse(false);
            ctx.setresponsestatuscode(401);
            ctx.setresponsebody("refuse! token is empty");
        }
        return null;
    }

}

router/routelocator.java & 路由类

public class routelocator extends simpleroutelocator implements refreshableroutelocator {

    private zuulproperties properties;

    public routelocator(string servletpath, zuulproperties properties) {
        super(servletpath, properties);
        this.properties = properties;
    }

    @override
    public void refresh() {
        dorefresh();
    }

    @override
    protected map<string, zuulroute> locateroutes() {
        linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>();
        //从application.properties中加载路由信息
        routesmap.putall(super.locateroutes());
        //从db中加载路由信息
        routesmap.putall(routesconfiggroup());
        //优化一下配置
        linkedhashmap<string, zuulroute> values = new linkedhashmap<>();
        for (map.entry<string, zuulroute> entry : routesmap.entryset()) {
            string path = entry.getkey();
            // prepend with slash if not already present.
            if (!path.startswith("/")) {
                path = "/" + path;
            }
            if (stringutils.hastext(this.properties.getprefix())) {
                path = this.properties.getprefix() + path;
                if (!path.startswith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getvalue());
        }
        return values;
    }

    /**
     * 路由配置组,可以从数据库中读取
     * 基本配置与写在文件中配置类似,如下;
     * #  routes:
     * #    api-a:
     * #      path: /route-a/**
     * #      serviceid: itstack-demo-springcloud-feign
     * #    api-b:
     * #      path: /route-b/**
     * #      serviceid: itstack-demo-springcloud-ribbon
     * @return 配置组内容
     */
    private map<string, zuulroute> routesconfiggroup() {
        map<string, zuulroute> routes = new linkedhashmap<>();

        zuulroute zuulroute = new zuulroute();
        zuulroute.setid("route-a");
        zuulroute.setpath("/route-a/**");
        zuulroute.setserviceid("itstack-demo-springcloud-feign");
        // 如果使用了注册中心,那么可以根据serviceid进行访问。
        // zuulroute.seturl("http://localhost:9001");
        zuulroute.setretryable(false);
        zuulroute.setstripprefix(true);
        zuulroute.setsensitiveheaders(new hashset<>());

        routes.put(zuulroute.getpath(), zuulroute);

        return routes;
    }

}

service/refreshrouteservice.java & 路由刷新服务

@service
public class refreshrouteservice {

    @autowired
    private applicationeventpublisher publisher;

    @autowired
    private routelocator routelocator;

    public void refreshroute() {
        routesrefreshedevent routesrefreshedevent = new routesrefreshedevent(routelocator);
        publisher.publishevent(routesrefreshedevent);
    }

}

zuulapplication.java & 启动服务注意注解,另外提供了服务接口

@springbootapplication
@enablezuulproxy
@enableeurekaclient
@enablediscoveryclient
@restcontroller
public class zuulapplication {

    public static void main(string[] args) {
        springapplication.run(zuulapplication.class, args);
    }

    @bean
    public tokenfilter tokenfilter() {
        return new tokenfilter();
    }

    @autowired
    private refreshrouteservice refreshrouteservice;
    @autowired
    private zuulhandlermapping zuulhandlermapping;

    @requestmapping("api/refresh")
    public string refresh(){
        refreshrouteservice.refreshroute();
        return "success";
    }

    @requestmapping("api/queryrouteinfo")
    @responsebody
    public map<string, object> queryrouteinfo(){
        return zuulhandlermapping.gethandlermap();
    }

}

application.yml & 配置文件修改,路由过滤

server:
  port: 10001

spring:
  application:
    name: itstack-demo-ddd-zuul

eureka:
  client:
    serviceurl:
      defaultzone: http://localhost:7397/eureka/

# 动态路由,以下配置注释;
# http://localhost:10001/route-a/api/queryuserinfo?userid=111
# http://localhost:10001/route-b/api/queryuserinfo?userid=111
zuul:
   ignoredpatterns: /api/**
#  routes:
#    api-a:
#      path: /route-a/**
#      serviceid: itstack-demo-springcloud-feign
#    api-b:
#      path: /route-b/**
#      serviceid: itstack-demo-springcloud-ribbon

测试验证

1.分别启动如下服务;

  • itstack-demo-springcloud-eureka-server 服务注册与发现
  • itstack-demo-springcloud-eureka-client 接口提供方
  • itstack-demo-springcloud-hystrix-feign 调用端
  • itstack-demo-springcloud-hystrix-ribbon 调用端
  • itstack-demo-springcloud-zuul 路由服务

2.可测试接口列表;

  • 路由服务:http://localhost:10001/route-a/api/queryuserinfo?userid=111&token=111

hello | 111 >: from eureka client port: 8001 from feign

  • 刷新配置:http://localhost:10001/api/refresh
  • 内容配置:http://localhost:10001/api/queryrouteinfo

综上总结

路由服务可以方便的帮我们控制业务类型的区分访问,同时自动刷新可以更加方便的使用网关路由

权限验证是几乎不可少的在实际开发过程中会经常用到,所有的接口必须是安全可靠的,保证数据不泄露

另外还可以考虑从入参的用户身份进行路由,这样可以把数据库路由提前,让不同用户组直接访问到不同的数据库组

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。