springcloud——gateway功能拓展

目录

1.获取用户真实IP

2.统一跨域配置

3.redis令牌桶算法限流


1.获取用户真实IP

在我们的日常业务中,我们时常需要获取用户的IP地址,作登录日志、访问限制等相关操作。

而在我们的开发架构中,一般我们将服务分为多个微服务,然后使用一个统一的网关对他们进行路由控制管理:

springcloud——gateway功能拓展_第1张图片

如上图,我们可以看到,一般来说网关(一般使用ngnix或者springcloud gateway)会放在独立的一台服务器上,他的ip是不一样的。当用户请求发过来时,网关收到用户请求,然后根据路由匹配对应的微服务,使用feign调用对应的微服务,所以在微服务中获取的ip其实是网关的IP,而不是用户访问的真实IP。

所以,我们想要获取用户的真实IP有以下两个方法:

(1)在gateway中进行配置:

我们可以在springcloud gateway中的过滤器中,拦截用户请求,获取用户的真实ip后存入HTTP header中,再转发至微服务中。

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class CommonFilter implements GlobalFilter {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest().mutate()
                //将获取的真实ip存入header微服务方便获取
                .header("X-Real-IP",exchange.getRequest().getRemoteAddress().getHostString())
                .build();
        return chain.filter(exchange.mutate().request(request).build());
    }

}

上述代码中奖用户的请求IP作为key:X-Real-IP的值存储到header中,然后微服务中通过获取该header的方法即可获取到用户的真实IP。

String ip = request.getHeader("X-Real-IP");

注:X-Real-IP,一般只记录真实发出请求的客户端IP。该字段不是header中自带的,需要自行在网关中进行添加配置(如上述代码)。

(2)通过转发IP列表获取:

public class IpUtil {

    public static String getIpAddress(HttpServletRequest request) {
        //目前则是网关ip
        String ip = request.getHeader("X-Forwarded-For");
        if (ip != null && !"".equals(ip) && !"unknown".equalsIgnoreCase(ip)) {
            int index = ip.indexOf(',');
            if (index != -1) {
                //只获取第一个值
                return ip.substring(0, index);
            } else {
                return ip;
            }
        } else {
            //取不到真实ip则返回空,不能返回内网地址。
            return null;
        }
    }

}

X-Forwarded-For是用于记录代理信息的,每经过一级代理,该字段就会记录来源地址,经过多级代理,服务端就会记录每级代理的X-Forwarded-For信息,IP之间以“,”分隔开。 

所以,我们只要获取X-Forwarded-For中的第一个IP,就是用户的真实IP。

(3)测试

最后,我们可以在微服务的controller中编写代码测试一下,看看获取到的ip是怎样的:

    @GetMapping("/test")
    public Map test(HttpServletRequest request){
        Map map = new HashMap<>();
        map.put("真实ip",request.getHeader("X-Real-IP"));
        map.put("ip列表",request.getHeader("X-Forwarded-For"));
        map.put("转发ip",request.getRemoteAddr());
        return map;
    }

springcloud——gateway功能拓展_第2张图片 

可以看到,在微服务直接使用 request.getRemoteAddr()获取到的只是网关所在的地址(此处是一个内网地址),而不是真实IP。而我们通过网关配置,再使用request.getHeader("X-Real-IP")获取到的才是真实IP。request.getHeader("X-Forwarded-For")中获取到的IP列表中,由于只进行了springcloud gateway一次代理,只记录了第一次代理前的IP地址。其与真实IP是一致的,所以X-Forwarded-For中的第一个IP地址也是用户的真实IP地址。

2.统一跨域配置

跨域问题就是由于前端服务器和后端服务器的IP地址、域名、端口、子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。

注意:跨域限制访问,其实是浏览器的限制

而在springcloud gateway中我们可以通过统一配置,对其访问的所有路由进行跨域统一处理:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        // 默认可不设置这个暴露的头。这个为了安全问题,不能使用*。
        // 设置成*,后面会报错:throw new IllegalArgumentException("'*' is not a valid exposed header value");
        corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL);
        corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
        corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(source);
    }

}

3.redis令牌桶算法限流

由于网关会外界访问系统的统一入口,所以我们一般需要在网关对请求进行引流或者直接拒绝等操作,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。

令牌桶算法:

令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中(redis),令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;

当网关收到一个请求时,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

springcloud gateway中为我们集成了基于redis的令牌桶算法,其实现方式十分简单:

server:
  port: 9527
  max-http-header-size: 102400
spring:
  application:
    name: cloud-gateway
    gateway: # 配置 Spring Cloud Gateway 相关属性
      discovery: # 配置网关发现机制
        locator: # 配置处理机制
          enabled: true # 开启网关自动映射处理逻辑
          lower-case-service-id: true # 开启服务名称小写转换。
      routes:  # 配置网关中的一个完整路由,包括命名,地址,谓词集合(规则),过滤器集合
        - id: user_student_routh # 路由定义的命名,唯一即可。
          uri: lb://cloud-student-manage # 当前路由定义对应的微服务转发地址,lb - 代表loadbalance
          predicates:
            - Path=/student/**   # 断言,路径相匹配的进行路由
          filters: # 配置过滤器集合
            - name: RequestRateLimiter
              args:
                keyResolver: '#{@myKeyResolver}'  # 使用SpringEL表达式,从Spring容器中找对象,并赋值。 '#{@beanName}',服务降级
                redis-rate-limiter.replenishRate: 100  # 生产令牌速度,每秒多少个令牌
                redis-rate-limiter.burstCapacity: 200  # 令牌桶容量
  redis:
    database: 0
    host: 127.0.0.1
    #redis默认端口
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 5000ms

只需要在application中配置对应的过滤器即可。

上述代码中还配置了当请求被拒绝时的服务降级相关配置,需要进行相关代码的编写:

(1)服务降级hystrix相关依赖

        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        

(2)服务降级配置:

@Component
public class MyKeyResolver implements KeyResolver {
    @Override
    public Mono resolve(ServerWebExchange exchange) {
        String remoteAddr = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        return Mono.just(remoteAddr);
    }
}

(3)服务降级接口:

@RestController
@Slf4j
public class AuthController {

    @RequestMapping(value="/downgrade")
    public CommonResult downgrade(Throwable e){
        return new CommonResult<>(444,"对不起,服务器繁忙,请稍后重试",e.getMessage());
    }

} 
  

                            
                        
                    
                    
                    

你可能感兴趣的:(spring,cloud,gateway,分布式,后端)