Springcloud之gateway配置及swagger集成

前言

关于引入gateway的好处我网上找了下:

  • 性能:API高可用,负载均衡,容错机制。
  • 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
  • 日志:日志记录(spainid,traceid)一旦涉及分布式,全链路跟踪必不可少。
  • 缓存:数据缓存。监控:记录请求响应数据,api耗时分析,性能监控。
  • 限流:流量控制,错峰流控,可以定义多种限流规则。
  • 灰度:线上灰度部署,可以减小风险。
  • 路由:动态路由规则。

配置

1.png

依赖

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;
    }
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值