目录

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 downgrade(Throwable e){        return new CommonResult(444,"对不起,服务器繁忙,请稍后重试",e.getMessage());    }}
Copyright © maxssl.com 版权所有 浙ICP备2022011180号