SpringCloudGateway配置路由规则与服务发现的路由规则功能冲突 | Java Debug 笔记

本文正在参加「Java主题月 – Java Debug笔记活动」,详情查看 活动链接

背景

事情是这样的我想做个自定义拦截器,在每次请求SpringCloudGateway服务时,gateway拦截到特定的url,去做用户认证鉴权,看是否有操作url的权限。

然而发现在application.yml配置路由规则不生效。如:我想访问gateway的ip:port/user-server/UserService/login 时,想去做鉴权(RequestAuthHeaderGateway里实现),但是没有被拦截到。

注: user-server是我一个springcloud服务的spring.application.name=的值

server:
  port: 80
  
spring:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true # 服务名小写
          enabled: true # 表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router
      routes:
        - id: user
          uri: lb://user-server
          predicates:
            - Path=/user-server/**
          filters:
            - StripPrefix=1
            - RequestAuthHeader
        
复制代码

出现的问题

访问springcloud组件的gateway的ip:port/user-server/login,并没有被application.yml里的routes的拦截规则Path给拦截到。大家猜猜看看什么原因。

解决问题的前提知识

  1. 要了解GatewayFilter实现方式

  2. 要了解GatewayFilterFactory实现方式

  3. nacos注册机制

  4. 了解SpringCloudGateway开启发现注册中心其他服务,并为所有服务创建默认路由规则。

规则命名为/服务名字/*,就能调用到服务下的/*的url

spring:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true # 服务名小写
          enabled: true # 表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router
复制代码
  1. 要知道Path里填写的是,gateway服务ip:port后面的全部uri
spring:
  cloud:
    gateway:
      routes:
        - id: user
          uri: lb://user-server
          predicates:
            - Path=/user-server/**
          filters:
            - StripPrefix=1
            - RequestAuthHeader
复制代码
  1. 自定义的拦截器的代码

这个是我的自定义GatewayFilterFactory代码,用于实现拦截认证的逻辑。这个代码逻辑没问题。

public class RequestAuthHeaderGatewayFilterFactory
        extends AbstractGatewayFilterFactory<RequestAuthHeaderGatewayFilterFactory.Config> {
    
    public RequestAuthHeaderGatewayFilterFactory() {
        super(Config.class);
    }
    
    public static class Config {
        // TODO: relaxed HttpStatus converter
        private String status;

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new RequestAuthHeaderFilter();
    }

}
复制代码

排查问题阶段

  1. 先找到自己代码被调用的地方

在自定义的RequestAuthHeaderGatewayFilterFactory中,对apply方法进行打断点。

    @Override
    public GatewayFilter apply(Config config) {
        return new RequestAuthHeaderFilter();
    }
复制代码

发现被RouteDefinitionRouteLocator类loadGatewayFilters这个方法调用。

@SuppressWarnings("unchecked")
	List<GatewayFilter> loadGatewayFilters(String id,
			List<FilterDefinition> filterDefinitions) {
		ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
		for (int i = 0; i < filterDefinitions.size(); i++) {
			FilterDefinition definition = filterDefinitions.get(i);
			GatewayFilterFactory factory = this.gatewayFilterFactories
					.get(definition.getName());
			if (factory == null) {
				throw new IllegalArgumentException(
						"Unable to find GatewayFilterFactory with name "
								+ definition.getName());
			}
			if (logger.isDebugEnabled()) {
				logger.debug("RouteDefinition " + id + " applying filter "
						+ definition.getArgs() + " to " + definition.getName());
			}

			// @formatter:off
			Object configuration = this.configurationService.with(factory)
					.name(definition.getName())
					.properties(definition.getArgs())
					.eventFunction((bound, properties) -> new FilterArgsEvent(
							// TODO: why explicit cast needed or java compile fails
							RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
					.bind();
			// @formatter:on

			// some filters require routeId
			// TODO: is there a better place to apply this?
			if (configuration instanceof HasRouteId) {
				HasRouteId hasRouteId = (HasRouteId) configuration;
				hasRouteId.setRouteId(id);
			}

                        // 就在这里调用我们自定义的GateWayFactory类
			GatewayFilter gatewayFilter = factory.apply(configuration);
			if (gatewayFilter instanceof Ordered) {
				ordered.add(gatewayFilter);
			}
			else {
				ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
			}
		}

		return ordered;
	}
复制代码

可以看出在yml里配置的routes拦截器是在这里进行注册。让url在这里匹配命中的拦截器,就是yml里一个id下的每个gatewayFilter实现类。

  1. 对被调地方进行调查机制

对loadGatewayFilters方法第一行进行打断点。发现,这个方法时定时执行的。

loadGatewayFilters(String id,List<FilterDefinition> filterDefinitions) 
复制代码

然而他定时的去做什么呢?
我再看到这个方法的参数id,参数正是我们nacos里所有服务名字。看上面的第二个参数,filterDefinitions是要处理的gateWayfilter。filterDefinitions看这个参数的内容。

[FilterDefinition{name='RewritePath', args={regexp=/properties-server/(?<remaining>.*), replacement=/${remaining}}}]
复制代码
  1. 发现可疑

我们发现他执行了RewritePathGatewayFilter,拦截的路径为/user-server。这个RewritePathGatewayFilter是用于改写url的。

可是看我之前application.yml里面根本没有配置RewritePath,不会去重写url。那这个重写url是谁操作的呢?

看他的重写正则表达式的规则,用正则表达式 把/user-server/** 内容,替换为/** 。

如:

/user-server/**
复制代码

路径,全部替换为

/**
复制代码
  1. 发现规律

正好就是SpringCloudGateway的另一个机制,为所有的nacos里的服务,对其servicename生成一个虚拟域名,作为负载均衡的名字,调用这个名字时即可获得这个服务的其中一个ip。这个也是restTemplate/feign调用远程服务时,所需要的填写的一个名字。

然而这个机制被gateway服务执行,则证明是springcloudgateway机制所执行上述定时任务,来作为gateway服务调用其他微服务的机制。

发现正是因为下述代码,开启了gateway定位其他服务的功能了。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true # 服务名小写
          enabled: true # 表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router
复制代码
  1. 找到冲突原因

就是这里和我配置的application.yml里的所有routes中,Path正好带有服务名的url,发生了冲突。

因为我application.yml的routes填写的path是/user-server/** ,正好与上述代码中enabled开启了gateway服务发现功能,导致执行RewritePathGatewayFilter重写url功能时,匹配的也正好是/user-server/** ,最终形成两个功能冲突

解决方案

方式1.

自己配置application.yml中的routes,匹配的url不要带服务名字。这个方式,可以开启spring.cloud.gateway.locator.enable=true功能。这样就不会形成因路径问题,而形成的冲突。

方式2.

application.yml中不开启gateway的locator功能。因为站在gateway是统一对外网的服务。不应该提供这个功能,因为一旦已开启,nacos的每个服务,都能被访问到,形成对外网的风险。

其次而且根据/服务名/** ,这样的形式访问,不会被routes配置的拦截规则给拦截到,本文就是描述这个问题的原因。

建议只使用routes里配置的规则,这种做法虽然在这个时候Path里填写的路径中,包含不包含服务名字都可以被拦截。但是我还是建议Path不包含服务名字,以免被外网的人,知道服务名字信息。作为减少信息泄露。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享