• Spring Cloud Gateway从数据库读取并更新Cors配置


    Spring Cloud Gateway从数据库读取并更新Cors配置

    由于运维特殊性,我们没有使用配置中心,仅仅只是使用了Nacos作为注册中心。目前项目gateway网关有个小需求,需要从数据库读取Cors跨域配置,刷新到应用中。

    分析源码

    Spring Cloud Gateway启动时,会通过GatewayAutoConfiguration配置需求创建的bean.在创建的RoutePredicateHandlerMapping bean时,在构造方法里,通过调用父类的setCorsConfigurations()方法更新或初始化Cors跨域配置。

    org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#RoutePredicateHandlerMapping

    	public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator,
    			GlobalCorsProperties globalCorsProperties, Environment environment) {
    		this.webHandler = webHandler;
    		this.routeLocator = routeLocator;
    
    		this.managementPort = getPortProperty(environment, "management.server.");
    		this.managementPortType = getManagementPortType(environment);
    		setOrder(environment.getProperty(GatewayProperties.PREFIX + ".handler-mapping.order", Integer.class, 1));
    		setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    AbstractHandlerMapping是RoutePredicateHandlerMapping的抽象父类
    org.springframework.web.reactive.handler.AbstractHandlerMapping#setCorsConfigurations

    	public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
    		Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
    		if (!corsConfigurations.isEmpty()) {
    			UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(this.patternParser);
    			source.setCorsConfigurations(corsConfigurations);
    			this.corsConfigurationSource = source;
    		}
    		else {
    			this.corsConfigurationSource = null;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    所以想更新Gateway网关的Cors跨域配置,可能通过引用RoutePredicateHandlerMapping bean,调用setCorsConfigurations刷新网关服务的cors跨域配置。

    实现步骤

    1. 从数据库从读取Cors配置
    2. 更新Spring 上下文中 GlobalCorsProperties实例bean的值
    3. 通过RoutePredicateHandlerMapping提供的setCorsConfigurations()方法刷新Cors跨域配置

    数据库设计

    根据 org.springframework.web.cors.CorsConfiguration实体类考虑需求处理的字段:configPath路径、allowedOrigins、allowedMethods、allowedHeaders、exposedHeaders、allowCredentials、maxAge。
    考虑到GlobalCorsProperties使用Map是LinkedHashMap,应该是有序的,添加了corsOrder字段,作为排序,添加Cors配置的顺序。

    CREATE TABLE `t_cors_config` (
      `config_id` varchar(100) NOT NULL,
      `config_name` varchar(100) DEFAULT NULL COMMENT '配置名称',
      `config_path` varchar(500) NOT NULL COMMENT '配置路径',
      `allowed_origins` text COMMENT '允许哪些网站的跨域请求',
      `allowed_methods` text COMMENT '允许的跨域请求方式',
      `allowed_headers` varchar(100) DEFAULT NULL COMMENT '允许在请求中携带的头信息',
      `exposed_headers` varchar(100) DEFAULT NULL COMMENT '需要向客户端公开的请求头',
      `allow_credentials` tinyint(1) DEFAULT NULL COMMENT '是否允许携带cookie',
      `max_age` bigint(20) DEFAULT NULL COMMENT '这次跨域检测的有效期(单位秒)',
      `cors_order` int(11) DEFAULT NULL COMMENT 'cors配置顺序',
      `sys_create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
      `sys_update_time` timestamp NULL DEFAULT NULL,
      `sys_status` int(11) DEFAULT NULL COMMENT '数据标识',
      `sys_remark` varchar(500) DEFAULT NULL COMMENT '备注',
      PRIMARY KEY (`config_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跨域配置';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    把数据库的cors配置转为CorsConfiguration

    我在数据库的实体类,直接写了转换方法,根据当前实体实例转成CorsConfiguration的实例,具体转换如下

        public CorsConfiguration getCorsConfiguration() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            //允许哪些网站的跨域请求
            corsConfiguration.setAllowedOrigins(this.convert(allowedOrigins));
            //允许的跨域请求方式
            corsConfiguration.setAllowedMethods(this.convert(allowedMethods));
            //允许在请求中携带的头信息
            corsConfiguration.setAllowedHeaders(this.convert(allowedHeaders));
            //需要向客户端公开的请求头
            corsConfiguration.setExposedHeaders(this.convert(exposedHeaders));
    
            corsConfiguration.setAllowCredentials(allowCredentials);
            corsConfiguration.setMaxAge(maxAge);
    
            return corsConfiguration;
        }
    
        private List<String> convert(String text) {
            if (StringUtils.isBlank(text)) {
                return null;
            }
            if (CorsConfiguration.ALL.equals(text)) {
                List<String> list = new ArrayList<>();
                list.add(text);
                return list;
            }
            try {
                Yaml yaml = new Yaml();
                return yaml.loadAs(text, List.class);
            } catch (Exception e) {
                log.error("cors配置格式转换错误 configId={} configPath={} text={}", configId, configPath, text);
                log.error(e.getMessage(), e);
                throw new RuntimeException("cors配置格式转换错误 configId=" + configId);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    更新cors配置到实际处理逻辑

    通过RoutePredicateHandlerMapping提供的setCorsConfigurations()方法刷新Cors跨域配置

    整体刷新入口代码如下
    GlobalCorsProperties、RoutePredicateHandlerMapping直接通过注入即可,这2个bean已经在GatewayAutoConfiguration初始化,直接使用就可以了。

    至于采用什么时候刷新配置,大家可以根据自己的情况设计。我是通过一个版本表,当检查到数据库的版本号大于应用中的配置版本号时,刷新配置。

        /**
         * 刷新cors跨域配置
         */
        private void refreshCorsConfig() {
            //从数据库中读取Cors配置
            List<CorsConfig> corsConfigs = corsConfigRepository.getCorsConfigs();
            //更新cors跨域配置bean
            corsProperties.getCorsConfigurations().clear();
            //排序后处理,数值小在前,null值在后
            corsConfigs.stream().sorted(Comparator.comparing(CorsConfig::getCorsOrder, Comparator.nullsLast(Integer::compareTo)))
                    .forEach(o -> {
                        //CorsConfig转成CorsConfiguration更新到GlobalCorsProperties中,key是path,value是CorsConfiguration
                        corsProperties.getCorsConfigurations().put(o.getConfigPath(), o.getCorsConfiguration());
                    });
            handlerMapping.setCorsConfigurations(corsProperties.getCorsConfigurations());
            log.info("完成刷新网关cors跨域配置 总数 {}", corsProperties.getCorsConfigurations().size());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    其他说明

    1、在查看源码的时候,引入的版本是Spring Cloud Gateway 2.X,后续在引入3.X、4.X版本发现,CorsConfiguration多了个allowedOriginPatterns字段,如果需求这个字段的设置,自行补上就可以了。(跟SpringBoot版本有关)
    2、在查资料的时候,听网友说配置中心不支持刷新Cors配置,我在4.X Gateway发现多了CorsGatewayFilterApplicationListener,也在GatewayAutoConfiguration中初始化了,它实现了ApplicationListener,接收RefreshRoutesEvent事件,在更新路由的配置的同时,也会更新Cors跨域配置。
    所以说,在4.X版本的Gateway(Spring Cloud 2022.0.0-RC2后的版本),是支持配置中心刷新Cors跨域配置的。

    org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener#onApplicationEvent

    	@Override
    	public void onApplicationEvent(RefreshRoutesEvent event) {
    		routeDefinitionLocator.getRouteDefinitions().collectList().subscribe(routeDefinitions -> {
    			// pre-populate with pre-existing global cors configurations to combine with.
    			var corsConfigurations = new HashMap<>(globalCorsProperties.getCorsConfigurations());
    
    			routeDefinitions.forEach(routeDefinition -> {
    				var corsConfiguration = getCorsConfiguration(routeDefinition);
    				corsConfiguration.ifPresent(configuration -> {
    					var pathPredicate = getPathPredicate(routeDefinition);
    					corsConfigurations.put(pathPredicate, configuration);
    				});
    			});
    
    			routePredicateHandlerMapping.setCorsConfigurations(corsConfigurations);
    		});
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    Python将.arff文件转换为.mat文件
    STM8应用笔记10.修改CPU的时钟
    java集合
    百万级别或以上的数据如何删除
    HiSilicon352 android9.0 适配红外遥控器
    Docker最基本使用
    网络安全宣传周|这些网络安全知识赶紧get起来~
    C语言之文件的使用(下)
    Pika v3.5.1发布!
    C++避坑小知识&&错题笔记
  • 原文地址:https://blog.csdn.net/huangliuyu00/article/details/128068140