Springcloud之gateway配置及swagger整合
阿新 • • 發佈:2020-12-06
前言
關於引入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: http://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: http://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) { exchange.getAttributes().put(START_TIME, System.currentTimeMillis()); ServerHttpRequest serverHttpRequest = exchange.getRequest(); String ip = IpUtil.getIp(serverHttpRequest); String method = serverHttpRequest.getMethod().name(); String requestURI = serverHttpRequest.getURI().getPath(); String token = serverHttpRequest.getHeaders().getFirst("token"); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(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 = exchange.getRequest();
String requestURI = "/api/admin"+request.getURI().getPath();
if(gatewaySetting.getWhiteUrls().contains(requestURI)){
return chain.filter(exchange);
}
boolean isCookieToken = false;
String token = request.getHeaders().getFirst("token");
if(StringUtils.isEmpty(token)){
MultiValueMap<String, HttpCookie> cookieValueMap = request.getCookies();
log.debug("cookieValueMap===============>"+ JSON.toJSONString(cookieValueMap));
if(cookieValueMap.containsKey(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME)){
HttpCookie cookie = cookieValueMap.getFirst(GlobalConstant.ADMIN_LOGIN_TOKEN_COOKIE_NAME);
token = cookie.getValue();
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 = exchange.getRequest().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 = exchange.getResponse();
Result resp = Result.of(code,msg);
byte[] bits = JSON.toJSONString(resp).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定編碼,否則在瀏覽器中會中文亂碼
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
public class IpUtil {
private static final Log log = LogFactory.getLog(IpUtil.class);
public static String getIp(ServerHttpRequest request) {
String ip=null;
List<String> headers = request.getHeaders().get("X-Real-IP");
if(headers!=null&&headers.size()>=1)
ip = headers.get(0);
if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
log.debug(">>>>>>>>>>>>>>>>>>>>>X-Real-IP獲取到ip:"+ip);
return ip;
}
headers = request.getHeaders().get("X-Forwarded-For");
if (!StringUtils.isEmpty(headers) && headers.size()>=1) {
// 多次反向代理後會有多個IP值,第一個為真實IP。
ip = headers.get(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);
return request.getRemoteAddress().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<>(swaggerResources.get(), 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 = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
//String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
String referName = "後臺API";
String referUrl = exchange.getRequest().getHeaders().get("Referer").get(0);
if (referUrl.indexOf("=") > -1) {
referName = referUrl.split("=")[1];
}
String basePath = "";
try {
basePath = SwaggerProvider.moduleMap.get(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;
}
}