Springcloud之gateway配置及swagger集成前⾔
关于引⼊gateway的好处我⽹上了下:
性能:API⾼可⽤,负载均衡,容错机制。
安全:权限⾝份认证、脱敏,流量清洗,后端签名(保证全链路可信调⽤),⿊名单(⾮法调⽤的限制)。
⽇志:⽇志记录(spainid,traceid)⼀旦涉及分布式,全链路跟踪必不可少。
缓存:数据缓存。监控:记录请求响应数据,api耗时分析,性能监控。
限流:流量控制,错峰流控,可以定义多种限流规则。
灰度:线上灰度部署,可以减⼩风险。
路由:动态路由规则。
配置
依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compile('org.springframework.cloud:spring-cloud-starter-gateway')
compile("org.springframework.cloud:spring-cloud-starter-openfeign")
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
application加注解
@EnableFeignClients
@EnableDiscoveryClient
yml
ribbon:
ConnectTimeout: 60000
ReadTimeout: 60000
eureka:
enabled: true
spring:
profiles:
active: dev
application:
name: web-gateway
cloud:
gateway:
discovery:
locator:
enabled: false
lower-case-service-id: true
routes:
- id: pc-api
uri: lb://pc-api
order: -1
predicates:
- Path=/api/pc/**
filters:
- StripPrefix=2
-
SwaggerHeaderFilter
- id: admin-api
uri: lb://admin-api
order: -1
predicates:
- Path=/api/admin/**
filters:
- StripPrefix=2
- SwaggerHeaderFilter #swagger过滤器
- AdminAuthFilter=true #管理后台⾃定义过虑器
- id: open-api
uri: lb://open-api
order: -1
predicates:
- Path=/api/open/**
filters:
- StripPrefix=2
- SwaggerHeaderFilter
#⽩名单(不鉴权)
setting:
whiteUrls:
- "/api/admin/auth/login"
-
"/api/admin/v2/api-docs"
- "/api/pc/v2/api-docs"
- "/api/open/v2/api-docs"
---
spring:
profiles: dev
redis:
host: 10.10.10.35
port: 6379
password: root
eureka:
instance:
prefer-ip-address: true
#Eureka服务端在收到最后⼀次⼼跳之后等待的时间上限,单位为秒,超过则剔除
lease-expiration-duration-in-seconds: 30
#Eureka客户端向服务端发送⼼跳的时间间隔,单位为秒
lease-renewal-interval-in-seconds: 15
client:
serviceUrl:
defaultZone: 10.10.10.35:8761/eureka/
---
spring:
profiles: uat
redis:
host: 172.17.0.12
port: 6379
password: root
eureka:
instance:
prefer-ip-address: true
client:
serviceUrl:
defaultZone: 172.17.0.12:8761/eureka/
全局过滤
@Slf4j
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = Request();
String ip = Ip(serverHttpRequest);
String method = Method().name();
String requestURI = URI().getPath();
String token = Headers().getFirst("token");
return chain.filter(exchange).then( Mono.fromRunnable(() -> {
Long startTime = Attribute(START_TIME);
if (startTime != null) {
Long executeTime = (System.currentTimeMillis() - startTime);
log.info(String.format("%s >>> %s >>> %s >>> %s >>> %s ms", requestURI, method, ip, token, executeTime));            }
}));
}
@Override
public int getOrder() {
return -2;
}
}
登录过滤
@Slf4j
@Component
public class AdminAuthFilter extends AbstractGatewayFilterFactory implements Ordered {
@Autowired
private GatewaySetting gatewaySetting;
@Autowired
private RedisUtil redisUtil;
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = Request();
String requestURI = "/api/admin"+URI().getPath();
WhiteUrls().contains(requestURI)){
return chain.filter(exchange);
}
boolean isCookieToken = false;
String token = Headers().getFirst("token");
if(StringUtils.isEmpty(token)){
MultiValueMap<String, HttpCookie> cookieValueMap = Cookies();
log.debug("cookieValueMap===============>"+ JSONString(cookieValueMap));
ainsKey(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME)){
HttpCookie cookie = First(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME);                    token = Value();
isCookieToken = true;
}
}
if(StringUtils.isEmpty(token)){
return FilterUtil.failResponse(exchange, Code.UNAUTHORIZED,"⾮法访问");
}
if(!redisUtil.hasKey(RedisKeyConstant.adminApiAuthLoginToken+token)){
return FilterUtil.failResponse(exchange,Code.EXPIRE_LOGIN,"登录过期");
}
if(isCookieToken){
ServerHttpRequest host = Request().mutate().header("token", token).build();
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);
}
return chain.filter(exchange);
};
}
@Override
public int getOrder() {
return 1;
}
}
⽩名单配置
@Getter
@Setter
@ConfigurationProperties("setting")
@Component
public class GatewaySetting {
private List<String> whiteUrls;
}
⼯具类
public class FilterUtil {
public static Mono<Void> failResponse(ServerWebExchange exchange, Code code, String msg){
ServerHttpResponse response = Response();
Result resp = Result.of(code,msg);
byte[] bits = JSONString(resp).getBytes(StandardCharsets.UTF_8);springcloud和springboot
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中⽂乱码
return response.writeWith(Mono.just(buffer));
}
}
public class IpUtil {
private static final Log log = Log(IpUtil.class);
public static String getIp(ServerHttpRequest request) {
String ip=null;
List<String> headers =  Headers().get("X-Real-IP");
if(headers!=null&&headers.size()>=1)
ip = (0);
if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
log.debug(">>>>>>>>>>>>>>>>>>>>>X-Real-IP获取到ip:"+ip);
return ip;
}
headers =  Headers().get("X-Forwarded-For");
if (!StringUtils.isEmpty(headers) && headers.size()>=1) {
// 多次反向代理后会有多个IP值,第⼀个为真实IP。
ip = (0);
int index = ip.indexOf(',');
if (index != -1) {
log.debug(">>>>>>>>>>>>>>>>>>>>>X-Forwarded-For获取到ip:"+ip);
return ip.substring(0, index);
} else {
return ip;
}
} else {
log.debug(">>>>>>>>>>>>>>>>>>>>>RemoteAddress获取到ip:"+ip);
RemoteAddress().getAddress().getHostAddress();
}
}
}
集成swagger
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));    }
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>((), HttpStatus.OK)));
}
}
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = Request();
String path = URI().getPath();
if (!dsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
//String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
String referName = "后台API";
String referUrl = Request().getHeaders().get("Referer").get(0);
if (referUrl.indexOf("=") > -1) {
referName = referUrl.split("=")[1];
}
String basePath = "";
try {
basePath = (URLDecoder.decode(referName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
@Component
@Primary
//@Profile({"dev","test"})
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = "/v2/api-docs";
public static Map<String, String> moduleMap = new HashMap<>();
static {
moduleMap.put("后台API", "/api/admin");
moduleMap.put("PC端API", "/api/pc");
moduleMap.put("开放平台", "/api/open");
}
@Override
public List<SwaggerResource> get() {
List resources = new ArrayList<>();
moduleMap.forEach((k, v) -> {
resources.add(swaggerResource(k, v));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location + API_URI);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}