init
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway;
|
||||
|
||||
import org.springblade.core.launch.BladeApplication;
|
||||
import org.springblade.core.launch.constant.AppConstant;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 项目启动
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@EnableScheduling
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
public class GateWayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BladeApplication.run(AppConstant.APPLICATION_GATEWAY_NAME, GateWayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.config;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springblade.gateway.handler.ErrorExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 异常处理配置类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(ErrorWebFluxAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({ServerProperties.class})
|
||||
public class ErrorHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
public ErrorExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) {
|
||||
return new ErrorExceptionHandler(objectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.config;
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.gateway.Knife4jGatewayProperties;
|
||||
import com.github.xiaoymin.knife4j.spring.gateway.discover.router.DiscoverClientRouteServiceConvert;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* knife4j自动聚合配置
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ReactiveDiscoveryConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = {"spring.cloud.gateway.server.webflux.discovery.locator.enabled"})
|
||||
public DiscoverClientRouteServiceConvert discoverClientRouteServiceConvert(DiscoveryClientRouteDefinitionLocator discoveryClient,
|
||||
Knife4jGatewayProperties knife4jGatewayProperties) {
|
||||
return new DiscoverClientRouteServiceConvert(discoveryClient, knife4jGatewayProperties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.gateway.filter.GatewayFilter;
|
||||
import org.springblade.gateway.props.AuthProperties;
|
||||
import org.springblade.gateway.props.RequestProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
/**
|
||||
* 路由配置信息
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AllArgsConstructor
|
||||
@EnableConfigurationProperties({AuthProperties.class, RequestProperties.class})
|
||||
public class RouterFunctionConfiguration {
|
||||
|
||||
/**
|
||||
* 全局配置
|
||||
*/
|
||||
@Bean
|
||||
public WebFilter gatewayFilter(RequestProperties requestProperties) {
|
||||
return new GatewayFilter(requestProperties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.dynamic;
|
||||
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动态路由业务类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
public class DynamicRouteService implements ApplicationEventPublisherAware {
|
||||
|
||||
private final RouteDefinitionWriter routeDefinitionWriter;
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) {
|
||||
this.routeDefinitionWriter = routeDefinitionWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.publisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加路由
|
||||
*/
|
||||
public String save(RouteDefinition definition) {
|
||||
try {
|
||||
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
return "save success";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "save failure";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路由
|
||||
*/
|
||||
public String update(RouteDefinition definition) {
|
||||
try {
|
||||
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
|
||||
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
return "update success";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "update failure";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路由
|
||||
*/
|
||||
public String updateList(List<RouteDefinition> routeDefinitions) {
|
||||
try {
|
||||
if (!routeDefinitions.isEmpty()) {
|
||||
routeDefinitions.forEach(this::update);
|
||||
}
|
||||
return "update done";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "update failure";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除路由
|
||||
*/
|
||||
public String delete(String id) {
|
||||
try {
|
||||
this.routeDefinitionWriter.delete(Mono.just(id));
|
||||
return "delete success";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "delete failure";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.dynamic;
|
||||
|
||||
import com.alibaba.cloud.nacos.NacosConfigProperties;
|
||||
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.nacos.api.NacosFactory;
|
||||
import com.alibaba.nacos.api.PropertyKeyConst;
|
||||
import com.alibaba.nacos.api.config.ConfigService;
|
||||
import com.alibaba.nacos.api.config.listener.Listener;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.launch.constant.NacosConstant;
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 动态路由监听器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Order
|
||||
@Slf4j
|
||||
@Component
|
||||
@RefreshScope
|
||||
public class DynamicRouteServiceListener {
|
||||
|
||||
private final DynamicRouteService dynamicRouteService;
|
||||
private final NacosDiscoveryProperties nacosDiscoveryProperties;
|
||||
private final NacosConfigProperties nacosConfigProperties;
|
||||
private final BladeProperties bladeProperties;
|
||||
|
||||
public DynamicRouteServiceListener(DynamicRouteService dynamicRouteService, NacosDiscoveryProperties nacosDiscoveryProperties, NacosConfigProperties nacosConfigProperties, BladeProperties bladeProperties) {
|
||||
this.dynamicRouteService = dynamicRouteService;
|
||||
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
|
||||
this.nacosConfigProperties = nacosConfigProperties;
|
||||
this.bladeProperties = bladeProperties;
|
||||
dynamicRouteServiceListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听Nacos下发的动态路由配置
|
||||
*/
|
||||
private void dynamicRouteServiceListener() {
|
||||
try {
|
||||
String dataId = bladeProperties.getName() + "-" + bladeProperties.getEnv() + "." + NacosConstant.NACOS_CONFIG_JSON_FORMAT;
|
||||
String group = nacosConfigProperties.getGroup();
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
|
||||
properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
|
||||
properties.setProperty(PropertyKeyConst.USERNAME, nacosDiscoveryProperties.getUsername());
|
||||
properties.setProperty(PropertyKeyConst.PASSWORD, nacosDiscoveryProperties.getPassword());
|
||||
ConfigService configService = NacosFactory.createConfigService(properties);
|
||||
configService.addListener(dataId, group, new Listener() {
|
||||
@Override
|
||||
public void receiveConfigInfo(String configInfo) {
|
||||
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
|
||||
if (!routeDefinitions.isEmpty()) {
|
||||
dynamicRouteService.updateList(routeDefinitions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
String configInfo = configService.getConfig(dataId, group, 5000);
|
||||
if (configInfo != null) {
|
||||
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
|
||||
dynamicRouteService.updateList(routeDefinitions);
|
||||
}
|
||||
} catch (NacosException ignored) {
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.dynamic;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 过滤器定义模型
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class GatewayFilter {
|
||||
|
||||
/**
|
||||
* 过滤器对应的Name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 对应的路由规则
|
||||
*/
|
||||
private Map<String, String> args = new LinkedHashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.dynamic;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 路由断言定义模型
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class GatewayPredicate {
|
||||
|
||||
/**
|
||||
* 断言对应的Name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 配置的断言规则
|
||||
*/
|
||||
private Map<String, String> args = new LinkedHashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.dynamic;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Gateway的路由定义模型
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class GatewayRoute {
|
||||
|
||||
/**
|
||||
* 路由的id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 路由断言集合配置
|
||||
*/
|
||||
private List<GatewayPredicate> predicates = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 路由过滤器集合配置
|
||||
*/
|
||||
private List<GatewayFilter> filters = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 路由规则转发的目标uri
|
||||
*/
|
||||
private String uri;
|
||||
|
||||
/**
|
||||
* 路由执行的顺序
|
||||
*/
|
||||
private int order = 0;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.filter;
|
||||
|
||||
import com.alibaba.nacos.common.utils.StringUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.jwt.JwtCrypto;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springblade.gateway.props.AuthProperties;
|
||||
import org.springblade.gateway.provider.AuthProvider;
|
||||
import org.springblade.gateway.provider.KeyProvider;
|
||||
import org.springblade.gateway.provider.RequestProvider;
|
||||
import org.springblade.gateway.provider.ResponseProvider;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.springblade.core.jwt.JwtCrypto.BLADE_TOKEN_CRYPTO_KEY;
|
||||
|
||||
/**
|
||||
* 鉴权认证
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class AuthFilter implements GlobalFilter, Ordered {
|
||||
private static final String MSG_TOKEN_MISSING = "缺失令牌,鉴权失败";
|
||||
private static final String MSG_REQUEST_UNAUTHORIZED = "请求未授权";
|
||||
private static final String MSG_TOKEN_EXPIRED = "令牌已失效";
|
||||
|
||||
private final AuthProperties authProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final JwtProperties jwtProperties;
|
||||
private final BladeProperties bladeProperties;
|
||||
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
//校验 Token 放行
|
||||
String originalRequestUrl = RequestProvider.getOriginalRequestUrl(exchange);
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
if (isSkip(path) || isSkip(originalRequestUrl)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
//校验 Token 合法性
|
||||
ServerHttpResponse resp = exchange.getResponse();
|
||||
String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
|
||||
String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
|
||||
if (StringUtils.isBlank(headerToken) && StringUtils.isBlank(paramToken)) {
|
||||
return unAuth(resp, MSG_TOKEN_MISSING);
|
||||
}
|
||||
String auth = StringUtils.isBlank(headerToken) ? paramToken : headerToken;
|
||||
// 判断 auth 是否为空
|
||||
if (StringUtils.isBlank(auth)) {
|
||||
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
|
||||
}
|
||||
// 校验 超级密钥 合法性,剩余属性交由微服务内部自行鉴权
|
||||
if (KeyProvider.isApiKey(auth, bladeProperties)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String token = JwtUtil.getToken(auth);
|
||||
// 判断 token 是否为空
|
||||
if (StringUtils.isBlank(token)) {
|
||||
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
|
||||
}
|
||||
// 校验 加密Token 合法性
|
||||
if (JwtUtil.isCrypto(auth)) {
|
||||
token = JwtCrypto.decryptToString(token, bladeProperties.getEnvironment().getProperty(BLADE_TOKEN_CRYPTO_KEY));
|
||||
}
|
||||
Claims claims = JwtUtil.parseJWT(token);
|
||||
if (token == null || claims == null) {
|
||||
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
|
||||
}
|
||||
// 判断 Token 状态
|
||||
if (jwtProperties.getState()) {
|
||||
String tenantId = String.valueOf(claims.get(TokenConstant.TENANT_ID));
|
||||
String clientId = String.valueOf(claims.get(TokenConstant.CLIENT_ID));
|
||||
String userId = String.valueOf(claims.get(TokenConstant.USER_ID));
|
||||
String accessToken = JwtUtil.getAccessToken(tenantId, clientId, userId, token);
|
||||
if (!token.equalsIgnoreCase(accessToken)) {
|
||||
return unAuth(resp, MSG_TOKEN_EXPIRED);
|
||||
}
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private boolean isSkip(String path) {
|
||||
return AuthProvider.getDefaultSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
|
||||
|| authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
|
||||
|| authProperties.getAuth().stream().anyMatch(auth -> antPathMatcher.match(auth.getPattern(), path))
|
||||
|| authProperties.getBasic().stream().anyMatch(basic -> antPathMatcher.match(basic.getPattern(), path))
|
||||
|| authProperties.getSign().stream().anyMatch(sign -> antPathMatcher.match(sign.getPattern(), path));
|
||||
}
|
||||
|
||||
private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
|
||||
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
||||
String result = "";
|
||||
try {
|
||||
result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
|
||||
return resp.writeWith(Flux.just(buffer));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -100;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.filter;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.gateway.props.RequestProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.web.cors.reactive.CorsUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 全局拦截器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class GatewayFilter implements WebFilter, Ordered {
|
||||
|
||||
/**
|
||||
* 请求配置
|
||||
*/
|
||||
private final RequestProperties requestProperties;
|
||||
/**
|
||||
* 路径匹配
|
||||
*/
|
||||
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
|
||||
/**
|
||||
* 默认拦截地址
|
||||
*/
|
||||
private final List<String> defaultBlockUrl = List.of("/**/actuator/**", "/health/**");
|
||||
/**
|
||||
* 默认白名单
|
||||
*/
|
||||
private final List<String> defaultWhiteList = List.of("127.0.0.1", "172.30.*.*", "192.168.*.*", "10.*.*.*", "0:0:0:0:0:0:0:1");
|
||||
/**
|
||||
* 默认提示信息
|
||||
*/
|
||||
private final static String DEFAULT_MESSAGE = "当前请求被拒绝,请联系管理员!";
|
||||
|
||||
/**
|
||||
* 这里为支持的请求头,如果有自定义的header字段请自己添加
|
||||
*/
|
||||
private static final String ALLOWED_HEADERS = "X-Requested-With, Tenant-Id, Blade-Auth, Content-Type, Authorization, credential, X-XSRF-TOKEN, token, username, client, knfie4j-gateway-request, knife4j-gateway-code, request-origion";
|
||||
private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
|
||||
private static final String ALLOWED_ORIGIN = "*";
|
||||
private static final String ALLOWED_EXPOSE = "*";
|
||||
private static final String MAX_AGE = "18000L";
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
// 处理跨域请求
|
||||
if (CorsUtils.isCorsRequest(request)) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
HttpHeaders headers = response.getHeaders();
|
||||
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
|
||||
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
|
||||
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
|
||||
headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
|
||||
headers.add("Access-Control-Max-Age", MAX_AGE);
|
||||
headers.add("Access-Control-Allow-Credentials", "true");
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
// 处理黑白名单与拦截请求
|
||||
if (requestProperties.getEnabled()) {
|
||||
String path = request.getPath().value();
|
||||
String ip = Objects.requireNonNull(request.getRemoteAddress()).getHostString();
|
||||
if (isRequestBlock(path, ip)) {
|
||||
throw new RuntimeException(DEFAULT_MESSAGE);
|
||||
}
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否白名单
|
||||
*
|
||||
* @param ip ip地址
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isWhiteList(String ip) {
|
||||
List<String> whiteList = requestProperties.getWhiteList();
|
||||
String[] defaultWhiteIps = defaultWhiteList.toArray(new String[0]);
|
||||
String[] whiteIps = whiteList.toArray(new String[0]);
|
||||
return PatternMatchUtils.simpleMatch(defaultWhiteIps, ip) || PatternMatchUtils.simpleMatch(whiteIps, ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否黑名单
|
||||
*
|
||||
* @param ip ip地址
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isBlackList(String ip) {
|
||||
List<String> blackList = requestProperties.getBlackList();
|
||||
String[] blackIps = blackList.toArray(new String[0]);
|
||||
return PatternMatchUtils.simpleMatch(blackIps, ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否禁用请求访问
|
||||
*
|
||||
* @param path 请求路径
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isRequestBlock(String path) {
|
||||
List<String> blockUrl = requestProperties.getBlockUrl();
|
||||
return defaultBlockUrl.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)) ||
|
||||
blockUrl.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否拦截请求
|
||||
*
|
||||
* @param path 请求路径
|
||||
* @param ip ip地址
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isRequestBlock(String path, String ip) {
|
||||
return (isRequestBlock(path) && !isWhiteList(ip)) || isBlackList(ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.springblade.gateway.filter;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 全局拦截器,作用所有的微服务
|
||||
* <p>
|
||||
* 1. 对请求头中参数进行处理 from 参数进行清洗
|
||||
* 2. 重写StripPrefix = 1,支持全局
|
||||
*
|
||||
* @author lengleng
|
||||
*/
|
||||
@Component
|
||||
public class RequestFilter implements GlobalFilter, Ordered {
|
||||
|
||||
/**
|
||||
* Process the Web request and (optionally) delegate to the next
|
||||
* {@code WebFilter} through the given {@link GatewayFilterChain}.
|
||||
*
|
||||
* @param exchange the current server exchange
|
||||
* @param chain provides a way to delegate to the next filter
|
||||
* @return {@code Mono<Void>} to indicate when request processing is complete
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 1. 清洗请求头中from 参数
|
||||
ServerHttpRequest request = exchange.getRequest().mutate()
|
||||
.headers(httpHeaders -> httpHeaders.remove("X"))
|
||||
.build();
|
||||
|
||||
// 2. 重写StripPrefix
|
||||
addOriginalRequestUrl(exchange, request.getURI());
|
||||
String rawPath = request.getURI().getRawPath();
|
||||
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
|
||||
.skip(1L).collect(Collectors.joining("/"));
|
||||
ServerHttpRequest newRequest = request.mutate()
|
||||
.path(newPath)
|
||||
.build();
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
|
||||
|
||||
return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.filter;
|
||||
|
||||
import com.alibaba.nacos.common.utils.StringUtils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.gateway.provider.AuthProvider;
|
||||
import org.springblade.gateway.provider.RequestProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* webflux 日志请求记录,方便开发调试。请求日志过滤器排序尽量低。
|
||||
*
|
||||
* <p>
|
||||
* 注意:暂时不支持结构体打印,想实现,请看下面的链接。
|
||||
* https://stackoverflow.com/questions/45240005/how-to-log-request-and-response-bodies-in-spring-webflux
|
||||
* https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging
|
||||
* </p>
|
||||
*
|
||||
* @author dream.lu
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class RequestLogFilter implements GlobalFilter, Ordered {
|
||||
private final WebEndpointProperties endpointProperties;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
// 打印请求路径
|
||||
String path = request.getPath().pathWithinApplication().value();
|
||||
|
||||
// 忽略 endpoint 请求
|
||||
String endpointBasePath = endpointProperties.getBasePath();
|
||||
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
String requestUrl = RequestProvider.getOriginalRequestUrl(exchange);
|
||||
|
||||
// 构建成一条长 日志,避免并发下日志错乱
|
||||
StringBuilder beforeReqLog = new StringBuilder(300);
|
||||
// 日志参数
|
||||
List<Object> beforeReqArgs = new ArrayList<>();
|
||||
beforeReqLog.append("\n\n================ Gateway Request Start ================\n");
|
||||
// 打印路由
|
||||
beforeReqLog.append("===> {}: {}\n");
|
||||
// 参数
|
||||
String requestMethod = request.getMethod().name();
|
||||
beforeReqArgs.add(requestMethod);
|
||||
beforeReqArgs.add(requestUrl);
|
||||
|
||||
// 打印请求头
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
headers.forEach((headerName, headerValue) -> {
|
||||
beforeReqLog.append("===Headers=== {}: {}\n");
|
||||
beforeReqArgs.add(headerName);
|
||||
if (AuthProvider.AUTH_KEY.toLowerCase().equals(headerName)) {
|
||||
String value = headerValue.get(0);
|
||||
String token = JwtUtil.getToken(value);
|
||||
Claims claims = JwtUtil.parseJWT(token);
|
||||
beforeReqArgs.add((claims == null) ? "" : claims.toString());
|
||||
beforeReqLog.append("===Headers=== {}: {}\n");
|
||||
beforeReqArgs.add(headerName.concat("-original"));
|
||||
beforeReqArgs.add(headerValue.toArray());
|
||||
} else {
|
||||
beforeReqArgs.add(headerValue.toArray());
|
||||
}
|
||||
});
|
||||
|
||||
beforeReqLog.append("================ Gateway Request End =================\n");
|
||||
// 打印执行时间
|
||||
log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.filter;
|
||||
|
||||
import com.alibaba.nacos.common.utils.StringUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* webflux 相应日志,方便开发调试,注意排序要优先。
|
||||
*
|
||||
* @author dream.lu
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class ResponseLogFilter implements GlobalFilter, Ordered {
|
||||
private final WebEndpointProperties endpointProperties;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
// 打印请求路径
|
||||
String path = request.getPath().pathWithinApplication().value();
|
||||
// 忽略 endpoint 请求
|
||||
String endpointBasePath = endpointProperties.getBasePath();
|
||||
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
return chain.filter(exchange).then(
|
||||
Mono.fromRunnable(() -> {
|
||||
MultiValueMap<String, String> queryParams = request.getQueryParams();
|
||||
String requestUrl = UriComponentsBuilder.fromPath(path).queryParams(queryParams).build().toUriString();
|
||||
|
||||
// 构建成一条长 日志,避免并发下日志错乱
|
||||
StringBuilder responseLog = new StringBuilder(300);
|
||||
// 日志参数
|
||||
List<Object> responseArgs = new ArrayList<>();
|
||||
responseLog.append("\n\n================ Gateway Response Start ================\n");
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
// 打印路由 200 get: /api/xxx/xxx
|
||||
responseLog.append("<=== {} {}: {}\n");
|
||||
// 参数
|
||||
String requestMethod = request.getMethod().name();
|
||||
responseArgs.add(response.getStatusCode().value());
|
||||
responseArgs.add(requestMethod);
|
||||
responseArgs.add(requestUrl);
|
||||
|
||||
// 打印请求头
|
||||
HttpHeaders headers = response.getHeaders();
|
||||
headers.forEach((headerName, headerValue) -> {
|
||||
responseLog.append("===Headers=== {}: {}\n");
|
||||
responseArgs.add(headerName);
|
||||
responseArgs.add(headerValue.toArray());
|
||||
});
|
||||
|
||||
responseLog.append("================ Gateway Response End =================\n");
|
||||
// 打印执行时间
|
||||
log.info(responseLog.toString(), responseArgs.toArray());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.gateway.provider.ResponseProvider;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 异常处理
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Order(-1)
|
||||
@RequiredArgsConstructor
|
||||
public class ErrorExceptionHandler implements ErrorWebExceptionHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (response.isCommitted()) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
if (ex instanceof ResponseStatusException) {
|
||||
response.setStatusCode(((ResponseStatusException) ex).getStatusCode());
|
||||
}
|
||||
|
||||
return response.writeWith(Mono.fromSupplier(() -> {
|
||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
||||
try {
|
||||
int status = 500;
|
||||
if (response.getStatusCode() != null) {
|
||||
status = response.getStatusCode().value();
|
||||
}
|
||||
Map<String, Object> result = ResponseProvider.response(status, this.buildMessage(request, ex));
|
||||
return bufferFactory.wrap(objectMapper.writeValueAsBytes(result));
|
||||
} catch (JsonProcessingException e) {
|
||||
return bufferFactory.wrap(new byte[0]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建异常信息
|
||||
*/
|
||||
private String buildMessage(ServerHttpRequest request, Throwable ex) {
|
||||
StringBuilder message = new StringBuilder("Failed to handle request [");
|
||||
message.append(request.getMethod().name());
|
||||
message.append(" ");
|
||||
message.append(request.getURI());
|
||||
message.append("]");
|
||||
if (ex != null) {
|
||||
message.append(": ");
|
||||
message.append(ex.getMessage());
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.props;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springblade.gateway.provider.AuthSecure;
|
||||
import org.springblade.gateway.provider.BasicSecure;
|
||||
import org.springblade.gateway.provider.SignSecure;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 权限过滤
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@ConfigurationProperties("blade.secure")
|
||||
public class AuthProperties {
|
||||
|
||||
/**
|
||||
* 放行API集合
|
||||
*/
|
||||
private final List<String> skipUrl = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 自定义授权配置
|
||||
*/
|
||||
private final List<AuthSecure> auth = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 基础认证配置
|
||||
*/
|
||||
private final List<BasicSecure> basic = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 签名认证配置
|
||||
*/
|
||||
private final List<SignSecure> sign = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.props;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request配置类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("blade.request")
|
||||
public class RequestProperties {
|
||||
|
||||
/**
|
||||
* 开启自定义request
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 放行url
|
||||
*/
|
||||
private List<String> skipUrl = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 禁用url
|
||||
*/
|
||||
private List<String> blockUrl = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 白名单,支持通配符,例如:10.20.0.8*、10.20.0.*
|
||||
*/
|
||||
private List<String> whiteList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 黑名单,支持通配符,例如:10.20.0.8*、10.20.0.*
|
||||
*/
|
||||
private List<String> blackList = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 鉴权配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class AuthProvider {
|
||||
|
||||
public static final String AUTH_KEY = TokenConstant.AUTH_HEADER;
|
||||
private static final List<String> DEFAULT_SKIP_URL = new ArrayList<>();
|
||||
|
||||
static {
|
||||
DEFAULT_SKIP_URL.add("/example");
|
||||
DEFAULT_SKIP_URL.add("/oauth/token/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/behavior/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/captcha/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/sms/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/clear-cache/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/user-info");
|
||||
DEFAULT_SKIP_URL.add("/oauth/render/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/callback/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/revoke/**");
|
||||
DEFAULT_SKIP_URL.add("/oauth/refresh/**");
|
||||
DEFAULT_SKIP_URL.add("/token/**");
|
||||
DEFAULT_SKIP_URL.add("/actuator/**");
|
||||
DEFAULT_SKIP_URL.add("/v3/api-docs/**");
|
||||
DEFAULT_SKIP_URL.add("/tenant/info");
|
||||
DEFAULT_SKIP_URL.add("/process/resource-view");
|
||||
DEFAULT_SKIP_URL.add("/process/diagram-view");
|
||||
DEFAULT_SKIP_URL.add("/manager/check-upload");
|
||||
DEFAULT_SKIP_URL.add("/assets/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认无需鉴权的API
|
||||
*/
|
||||
public static List<String> getDefaultSkipUrl() {
|
||||
return DEFAULT_SKIP_URL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 自定义授权规则
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuthSecure {
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 基础授权规则
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BasicSecure {
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 超级密钥加解密工具类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class KeyProvider {
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* API Key 前缀
|
||||
*/
|
||||
private static final String API_KEY_PREFIX = "ak-";
|
||||
/**
|
||||
* API Key 配置项
|
||||
*/
|
||||
private static final String BLADE_KEY_ENABLED = "blade.key.enabled";
|
||||
/**
|
||||
* API Key 配置项
|
||||
*/
|
||||
private static final String BLADE_KEY_CRYPTO_KEY = "blade.key.crypto-key";
|
||||
/**
|
||||
* 随机字符串因子
|
||||
*/
|
||||
private static final String RANDOM_FACTOR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
/**
|
||||
* 安全随机数生成器
|
||||
*/
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
/**
|
||||
* PKCS7 块大小
|
||||
*/
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
/**
|
||||
* Hex 编码字符
|
||||
*/
|
||||
private static final byte[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
/**
|
||||
* 判断是否为合法 API Key
|
||||
*
|
||||
* @param auth 认证字符串
|
||||
* @return 是否为 API Key
|
||||
*/
|
||||
public static boolean isApiKey(@Nullable String auth, BladeProperties bladeProperties) {
|
||||
// 检查功能是否启用
|
||||
String enabled = bladeProperties.getEnvironment().getProperty(BLADE_KEY_ENABLED);
|
||||
if (!Boolean.parseBoolean(enabled)) {
|
||||
return false;
|
||||
}
|
||||
// 检查前缀并解析
|
||||
if (auth != null && auth.startsWith(API_KEY_PREFIX)) {
|
||||
String cryptoKey = bladeProperties.getEnvironment().getProperty(BLADE_KEY_CRYPTO_KEY);
|
||||
String parsed = parseKey(auth, cryptoKey);
|
||||
return parsed != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 API Key
|
||||
*
|
||||
* @param cryptoKey 加密密钥
|
||||
* @return 带前缀的加密 Key
|
||||
*/
|
||||
public static String generateKey(String cryptoKey) {
|
||||
return API_KEY_PREFIX + encryptToHex(randomUUID(), cryptoKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 API Key
|
||||
*
|
||||
* @param key 带前缀的加密 Key
|
||||
* @param cryptoKey 加密密钥
|
||||
* @return 解密后的原始值
|
||||
*/
|
||||
@Nullable
|
||||
public static String parseKey(@Nullable String key, String cryptoKey) {
|
||||
if (isBlank(key) || !key.startsWith(API_KEY_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String encryptedPart = key.substring(API_KEY_PREFIX.length());
|
||||
return decryptFormHexToString(encryptedPart, cryptoKey);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密并转换为十六进制字符串
|
||||
*
|
||||
* @param content 明文内容
|
||||
* @param aesTextKey AES 密钥字符串
|
||||
* @return 十六进制加密字符串
|
||||
*/
|
||||
public static String encryptToHex(String content, String aesTextKey) {
|
||||
return hexEncode(encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从十六进制字符串解密并返回明文字符串
|
||||
*
|
||||
* @param content 十六进制加密字符串
|
||||
* @param aesTextKey AES 密钥字符串
|
||||
* @return 解密后的明文字符串
|
||||
*/
|
||||
@Nullable
|
||||
public static String decryptFormHexToString(@Nullable String content, String aesTextKey) {
|
||||
byte[] hexBytes = decryptFormHex(content, aesTextKey);
|
||||
if (hexBytes == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(hexBytes, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从十六进制字符串解密并返回字节数组
|
||||
*
|
||||
* @param content 十六进制加密字符串
|
||||
* @param aesTextKey AES 密钥字符串
|
||||
* @return 解密后的字节数组
|
||||
*/
|
||||
@Nullable
|
||||
public static byte[] decryptFormHex(@Nullable String content, String aesTextKey) {
|
||||
if (isBlank(content)) {
|
||||
return null;
|
||||
}
|
||||
return decrypt(hexDecode(content.getBytes(DEFAULT_CHARSET)), aesTextKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密字节数组
|
||||
*
|
||||
* @param content 明文字节数组
|
||||
* @param aesTextKey AES 密钥字符串
|
||||
* @return 加密后的字节数组
|
||||
*/
|
||||
public static byte[] encrypt(byte[] content, String aesTextKey) {
|
||||
return aes(pkcs7Encode(content), Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.ENCRYPT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密字节数组
|
||||
*
|
||||
* @param content 加密的字节数组
|
||||
* @param aesTextKey AES 密钥字符串
|
||||
* @return 解密后的字节数组
|
||||
*/
|
||||
public static byte[] decrypt(byte[] content, String aesTextKey) {
|
||||
return pkcs7Decode(aes(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.DECRYPT_MODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 加解密核心方法
|
||||
*/
|
||||
private static byte[] aes(byte[] data, byte[] aesKey, int mode) {
|
||||
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
|
||||
cipher.init(mode, keySpec, iv);
|
||||
return cipher.doFinal(data);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内联工具方法 ====================
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串
|
||||
*/
|
||||
public static String randomKey(int count) {
|
||||
char[] buffer = new char[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
buffer[i] = RANDOM_FACTOR.charAt(SECURE_RANDOM.nextInt(RANDOM_FACTOR.length()));
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成无横线的 UUID
|
||||
*/
|
||||
private static String randomUUID() {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
return new UUID(random.nextLong(), random.nextLong()).toString().replace("-", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否为空或空白
|
||||
*/
|
||||
private static boolean isBlank(@Nullable String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
if (!Character.isWhitespace(str.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转十六进制字符串
|
||||
*/
|
||||
private static String hexEncode(byte[] data) {
|
||||
int len = data.length;
|
||||
byte[] out = new byte[len << 1];
|
||||
for (int i = 0, j = 0; i < len; i++) {
|
||||
out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
|
||||
out[j++] = HEX_DIGITS[0x0F & data[i]];
|
||||
}
|
||||
return new String(out, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制字节数组解码为原始字节数组
|
||||
*/
|
||||
private static byte[] hexDecode(byte[] data) {
|
||||
int len = data.length;
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new IllegalArgumentException("hexBinary needs to be even-length: " + len);
|
||||
}
|
||||
byte[] out = new byte[len >> 1];
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = Character.digit(data[j++], 16) << 4;
|
||||
f |= Character.digit(data[j++], 16);
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCS7 编码(填充)
|
||||
*/
|
||||
private static byte[] pkcs7Encode(byte[] src) {
|
||||
int count = src.length;
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
byte pad = (byte) (amountToPad & 0xFF);
|
||||
byte[] dest = new byte[count + amountToPad];
|
||||
System.arraycopy(src, 0, dest, 0, count);
|
||||
Arrays.fill(dest, count, dest.length, pad);
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCS7 解码(去填充)
|
||||
*/
|
||||
private static byte[] pkcs7Decode(byte[] decrypted) {
|
||||
int pad = decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > BLOCK_SIZE) {
|
||||
pad = 0;
|
||||
}
|
||||
if (pad > 0) {
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
/**
|
||||
* RequestProvider
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class RequestProvider {
|
||||
|
||||
/**
|
||||
* 获取原始url
|
||||
*
|
||||
* @param exchange
|
||||
* @return
|
||||
*/
|
||||
public static String getOriginalRequestUrl(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
|
||||
URI requestUri = uris.stream().findFirst().orElse(request.getURI());
|
||||
MultiValueMap<String, String> queryParams = request.getQueryParams();
|
||||
return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 请求响应返回
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ResponseProvider {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*
|
||||
* @param message 信息
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> success(String message) {
|
||||
return response(200, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param message 信息
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> fail(String message) {
|
||||
return response(400, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权
|
||||
*
|
||||
* @param message 信息
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> unAuth(String message) {
|
||||
return response(401, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器异常
|
||||
*
|
||||
* @param message 信息
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> error(String message) {
|
||||
return response(500, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建返回的JSON数据格式
|
||||
*
|
||||
* @param status 状态码
|
||||
* @param message 信息
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> response(int status, String message) {
|
||||
Map<String, Object> map = new HashMap<>(16);
|
||||
map.put("code", status);
|
||||
map.put("msg", message);
|
||||
map.put("data", null);
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.gateway.provider;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 签名授权规则
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SignSecure {
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String pattern;
|
||||
|
||||
}
|
||||
11
blade-gateway/src/main/resources/application-dev.yml
Normal file
11
blade-gateway/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
blade:
|
||||
#多团队协作服务配置
|
||||
loadbalancer:
|
||||
#开启配置
|
||||
enabled: true
|
||||
#灰度版本
|
||||
#version: 3.0.0
|
||||
#负载均衡优先调用的ip段
|
||||
prior-ip-pattern:
|
||||
- 192.168.0.*
|
||||
- 127.0.0.1
|
||||
11
blade-gateway/src/main/resources/application-prod.yml
Normal file
11
blade-gateway/src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
blade:
|
||||
#多团队协作服务配置
|
||||
loadbalancer:
|
||||
#开启配置
|
||||
enabled: true
|
||||
#灰度版本
|
||||
#version: 3.0.0
|
||||
#负载均衡优先调用的ip段
|
||||
prior-ip-pattern:
|
||||
- 192.168.0.*
|
||||
- 127.0.0.1
|
||||
11
blade-gateway/src/main/resources/application-test.yml
Normal file
11
blade-gateway/src/main/resources/application-test.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
blade:
|
||||
#多团队协作服务配置
|
||||
loadbalancer:
|
||||
#开启配置
|
||||
enabled: true
|
||||
#灰度版本
|
||||
#version: 3.0.0
|
||||
#负载均衡优先调用的ip段
|
||||
prior-ip-pattern:
|
||||
- 192.168.0.*
|
||||
- 127.0.0.1
|
||||
16
blade-gateway/src/main/resources/application.yml
Normal file
16
blade-gateway/src/main/resources/application.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
knife4j:
|
||||
gateway:
|
||||
enabled: true
|
||||
tags-sorter: order
|
||||
operations-sorter: order
|
||||
# 指定服务发现的模式聚合微服务文档,并且是默认`default`分组
|
||||
strategy: discover
|
||||
discover:
|
||||
enabled: true
|
||||
# 指定版本号(Swagger2|OpenAPI3)
|
||||
version : openapi3
|
||||
# 需要排除的微服务(eg:网关服务)
|
||||
excluded-services:
|
||||
- blade-admin
|
||||
- blade-gateway
|
||||
- blade-log
|
||||
23
blade-gateway/src/main/resources/bootstrap.yml
Normal file
23
blade-gateway/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
server:
|
||||
port: 80
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
server:
|
||||
webflux:
|
||||
discovery:
|
||||
locator:
|
||||
enabled: true
|
||||
loadbalancer:
|
||||
retry:
|
||||
enabled: true
|
||||
|
||||
# 兼容性说明:新版gateway配置需要最后启动才能发现服务,否则会出现404的问题
|
||||
# 此问题可等spring官方修复,或临时处理方案采用下方旧版配置即可自动发现服务
|
||||
#spring:
|
||||
# cloud:
|
||||
# gateway:
|
||||
# discovery:
|
||||
# locator:
|
||||
# enabled: true
|
||||
Reference in New Issue
Block a user