目录
1.获取用户真实IP
2.统一跨域配置
3.redis令牌桶算法限流
1.获取用户真实IP
在我们的日常业务中,我们时常需要获取用户的IP地址,作登录日志、访问限制等相关操作。
而在我们的开发架构中,一般我们将服务分为多个微服务,然后使用一个统一的网关对他们进行路由控制管理:
如上图,我们可以看到,一般来说网关(一般使用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;@Componentpublic 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; }
可以看到,在微服务直接使用 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;@Configurationpublic 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: 102400spring: 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)服务降级配置:
@Componentpublic class MyKeyResolver implements KeyResolver { @Override public Mono resolve(ServerWebExchange exchange) { String remoteAddr = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); return Mono.just(remoteAddr); }}
(3)服务降级接口:
@RestController@Slf4jpublic class AuthController { @RequestMapping(value="/downgrade") public CommonResult