SpringCloudGateway微服务网关实战与源码分析 - 中

发布时间:2023-12-28 13:00

实战

路由过滤器工厂

路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器的作用域是特定的路由。SpringCloud Gateway包括许多内置的GatewayFilter工厂。目前官网提供33种路由过滤器工厂,前面示例中filters里的StripPrefix就是其中一种:

SpringCloudGateway微服务网关实战与源码分析 - 中_第1张图片

在库存微服务控制器中增加打印

    @RequestMapping("/deduct")
    public String storage(HttpServletRequest request){
        log.info("请求头X-Request-red:{}",request.getHeader("X-Request-red"));
        return "扣减库存";
    }

Nacos中网关的配置增加AddRequestHeader=X-Request-red, blue

server:
  port: 4090
spring:
  cloud:
    gateway:
      routes:
        - id: storage_route
          uri: lb://ecom-storage-service
          predicates:
            - Path=/storage-service/**
            - Quantity=100,200
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-red, blue

访问http://localhost:4090/storage-service/deduct?quantity=100 ,从库存微服务的日志可以看到网关的过滤器已经添加请求头信息

SpringCloudGateway微服务网关实战与源码分析 - 中_第2张图片

其他很多种过滤器实现各位有时间可以一一尝试

自定义局部过滤器工厂

建立一个授权的自定义工厂过滤器工厂,从redis读取授权信息验证,先创建RedisConfig配置类

package cn.itxs.ecom.gateway.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfig{
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        //template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(stringRedisSerializer);
        // hash的value序列化方式采用jackson
        //template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(stringRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

redis信息放在Nacos配置commons-dev.yaml里

SpringCloudGateway微服务网关实战与源码分析 - 中_第3张图片

创建过滤器工厂AuthorizeGatewayFilterFactory.java继承自AbstractGatewayFilterFactory

package cn.itxs.ecom.gateway.factory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;

@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {


    private static final String AUTHORIZE_TOKEN = "token";
    private static final String AUTHORIZE_UID = "uid";

    @Autowired
    private RedisTemplate redisTemplate;

    public AuthorizeGatewayFilterFactory() {
        super(Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            String uid = headers.getFirst(AUTHORIZE_UID);
            if (token == null) {
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }
            if (uid == null) {
                uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
            }

            ServerHttpResponse response = exchange.getResponse();
            if (!StringUtils.hasText(token) || !StringUtils.hasText(uid)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            String authToken = (String) redisTemplate.opsForValue().get(uid);
            if (authToken == null || !authToken.equals(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            return chain.filter(exchange);
        };
    }

    public static class Config {
        // 控制是否开启认证
        private boolean enabled;

        public Config() {}

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
}

如果授权不通过返回HttpStatus.UNAUTHORIZED也即是 http 401

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fDzQbqT-1657432430026)(http://www.itxiaoshen.com:3001/assets/1657431654370DW5dZAyW.png)]

网关Nacos配置文件增加Authorize=true

server:
  port: 4090
spring:
  cloud:
    gateway:
      routes:
        - id: storage_route
          uri: lb://ecom-storage-service
          predicates:
            - Path=/storage-service/**
            - Quantity=100,200
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-red, blue
            - Authorize=true

由于目前redis里面没有uid和token的信息,访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc 返回401状态

SpringCloudGateway微服务网关实战与源码分析 - 中_第4张图片

我们往redis写入uid为1001的数据

image-20220709133350294

再次访问http://localhost:4090/storage-service/deduct?quantity=100&uid=1001&token=abc ,则可以正常获取结果

image-20220709133310094

全局过滤器

  • 前面配置使用的lb就是全局过滤器来实现的。ReactiveLoadBalancerClientFilter在名为ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的交换属性中查找URI。如果URL有一个lb方案(例如lb://myservice),它使用Spring Cloud ReactorLoadBalancer来解析名称(在本例中是myservice)到一个实际的主机和端口,并替换相同属性中的URI。未修改的原始URL被追加到ServerWebExchangeUtils中的列表中。GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性。GATEWAY_SCHEME_PREFIX_ATTR属性来查看它是否等于lb。如果等于,则应用相同的规则。
  • 全局过滤器作为bean注册成功后,不需要进行配置,就可以直接生效。全局过滤器的作用范围是对所有的请求,而局部过滤器是针对路由。GlobalFilter 是用来定义全局过滤器的接口,通过实现GlobalFilter接口可以实现各种自定义过滤器。有多个拦截器时通过Ordered接口实现getOrder()方法来指定执行顺序,返回值越小执行顺序越靠前需要添加@Component注解;
  • 利用全局过滤器,可以实现统一的鉴权处理,日志处理等。
  • 在配置时可以通过spring.cloud.gateway.default-filters实现所配置的过滤器全局生效,但这种方法在实际中比较少用。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号