速通springcloud--04gateway

Gateway

创建网关

官网:https://docs.spring.io/spring-cloud-gateway

网关并不属于业务,在创建工程时最好放在与model层同级目录下

使用时,需要引入spring-cloud-starter-gateway,并且由于网关的工作目的是将请求根据不同uri路由到不同的微服务中,所以还需要nacos注册中心来知道所有微服务的地址spring-cloud-starter-alibaba-nacos-discovery,在配置文件中也需要配置配置中心的地址。

pom.xml

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
spring:
    application:
        name: gateway
    cloud:
        nacos:
            server-addr: 127.0.0.1:8848

# http默认访问80端口,之后访问网关可以不写端口
server:
    port: 80
@SpringBootApplication
public class GatewayMainApplication{
    public static void main(String[] args){
        SpringApplication.run(GatewayMainApplication.class,args);
    }
}

路由

spring:
    cloud:
        gateway:
            routes:
                - id: order-route
                  uri: lb://service-order
                  predicates:
                    - Path=/api/order/**
                - id: product-route
                  uri: lb://service-product
                  predicates:
                    - Path=/api/product/**

# 效果:访问localhost/api/order/1时,请求会被路由到service-order服务中的某一个服务器,并且是负载均衡的

配置路由规则,这里的id用以配置路由名,uri表示路由到的服务(这里的lb表示以负载均衡的方式进行路由),predicates表示路由需要满足的条件

这里由于使用了nacos的负载均衡,故要引入负载均衡场景spring-cloud-starter-loadbalancer

在spring-gateway中,默认情况下路由匹配是根据所配置的路由从上到下进行的,但同时也可以为添加的路由指定order属性来定义匹配顺序(数字越小优先级越高)

断言

即满足路由匹配的规则,在配置文件中用predicates:属性来进行描述,需要传入一个数组.

断言的匹配规则可以是时间、cookie、请求头、host、请求方法、uri等,具体可以参照官方文档https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html

自定义断言工厂

除开使用spring提供的那些断言工厂外,还可以自定义断言规则

例如,我需要自定义一个判断token是否为空的断言工厂

路由配置文件

predicates:
    - TokenNotEmpty=true
// 这里的类名必须是断言规则名加上RoutePredicateFactory
// 实际上,gateway默认的几种断言规则就是以这种形式进行命名的
// AbstractRoutePredicateFactory这个是抽象的断言工厂,所有的断言工厂都必须继承该类
// 这里传入了一个泛型,该泛型是一个内部类,用于定义断言规则,自定义断言工厂时,可以直接copy一个config然后自行修改
@Component  // 声明为Spring组件,使其能被自动扫描
public class TokenNotEmptyRoutePredicateFactory 
    extends AbstractRoutePredicateFactory<TokenNotEmptyRoutePredicateFactory.Config> {

    // 默认的token查询参数名
    private static final String TOKEN_KEY = "token";
    // 默认的授权头字段名
    private static final String HEADER_KEY = "Authorization";
    // Bearer Token的前缀
    private static final String BEARER_PREFIX = "Bearer ";

    /**
     * 构造函数,必须调用父类的构造函数并传入配置类
     */
    public TokenNotEmptyRoutePredicateFactory() {
        super(Config.class);
    }

    /**
     * 定义配置参数的快捷字段顺序
     * 用于YAML/Properties配置时的简写形式
     * @return 配置参数的字段名列表
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("required");
    }

    /**
     * 创建实际的断言逻辑
     * @param config 配置对象
     * @return 断言谓词
     */
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            /**
             * 测试方法,判断当前请求是否匹配该路由
             * @param exchange 服务器网络交换对象,包含请求/响应信息
             * @return 是否匹配路由
             */
            @Override
            public boolean test(ServerWebExchange exchange) {
                // 1. 首先检查请求头中的Authorization字段
                String authHeader = exchange.getRequest().getHeaders().getFirst(HEADER_KEY);
                
                // 检查Authorization头是否存在且以Bearer开头
                if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
                    // 提取Bearer后面的token部分
                    String token = authHeader.substring(BEARER_PREFIX.length());
                    if (StringUtils.hasText(token)) {
                        return true;  // 有效的Bearer Token
                    }
                }

                // 2. 检查查询参数中的token字段
                String tokenParam = exchange.getRequest().getQueryParams().getFirst(TOKEN_KEY);
                if (StringUtils.hasText(tokenParam)) {
                    return true;  // 有效的查询参数token
                }

                // 3. 如果以上都不满足,根据配置决定是否允许通过
                // required=true表示必须要有token,此时返回false
                // required=false表示不强制要求token,此时返回true
                return !config.isRequired();
            }

            /**
             * toString方法,用于日志和调试
             * @return 断言的字符串表示
             */
            @Override
            public String toString() {
                return String.format("TokenNotEmpty: required=%s", config.isRequired());
            }
        };
    }

    /**
     * 配置类,用于接收YAML/Properties中的配置参数
     */
    public static class Config {
        // 是否必须包含token,默认为true
        private boolean required = true;

        /**
         * 获取required配置值
         * @return 是否必须包含token
         */
        public boolean isRequired() {
            return required;
        }

        /**
         * 设置required配置值
         * @param required 是否必须包含token
         */
        public void setRequired(boolean required) {
            this.required = required;
        }
    }
}

过滤器

概述

该处的过滤器和springmvc中的过滤器非常类似,请求先按照顺序经过前置过滤器到达目的地,之后再按照逆序经过后置过滤器最后响应给请求方

SpringCloudGateway中内置了大量的过滤器工厂https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html

例如我想要将请求路径的/api去除掉

spring:
    cloud:
        gateway:
            filters:
                - RewritePath=/api/?(?<segment>.*), /$\{segment}

默认过滤器

默认过滤器(Default Filters)
是在全局路由配置中定义的过滤器,会应用到所有路由,而路由级别的filters支队特定路由生效。默认情况下default-filters优先级高于GlobalFilter(默认过滤器优先级通常为0)

spring:
    cloud:
        gateway:
            default-filters:
                - AddRequestParameter=default_param, 123

全局过滤器

全局过滤器(Global Filters)
通过实现GlobalFilter接口编写的过滤器,强制对所有请求生效
通常用于跨切面逻辑

例子:拦截所有未携带token的请求

@Component
@Order(-1)  // 控制执行顺序(数字越小,优先级越高,default-filters优先级通常为0)
public class AuthGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 检查请求头是否有 Token
        if (!exchange.getRequest().getHeaders().containsKey("Authorization")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); // 拦截请求
        }
        return chain.filter(exchange); // 放行
    }
}

自定义过滤器工厂

@Component
public class OneTimeTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<OneTimeTokenGatewayFilterFactory.Config> {

    // 存储有效令牌的缓存
    private static final Map<String, String> tokenCache = new ConcurrentHashMap<>();

    public OneTimeTokenGatewayFilterFactory() {
        super(Config.class);
    }

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

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

            // 从请求头获取令牌
            String token = exchange.getRequest().getHeaders().getFirst("X-One-Time-Token");
            
            if (!StringUtils.hasText(token)) {
                return unauthorizedResponse(exchange, "Missing one-time token");
            }

            // 验证令牌是否存在
            if (!tokenCache.containsKey(token)) {
                return unauthorizedResponse(exchange, "Invalid or expired one-time token");
            }

            // 移除已使用的令牌(一次性)
            tokenCache.remove(token);

            return chain.filter(exchange);
        };
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    // 生成一次性令牌的公共方法(可由其他服务调用)
    public static String generateToken(String value) {
        String token = java.util.UUID.randomUUID().toString();
        tokenCache.put(token, value);
        return token;
    }

    // 验证令牌的公共方法
    public static boolean validateToken(String token) {
        return tokenCache.containsKey(token);
    }

    public static class Config {
        private boolean enabled = false;

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
}
spring:
  cloud:
    gateway:
      routes:
        - id: secure-service
          uri: http://example.com
          predicates:
            - Path=/api/secure/**
          filters:
            - name: OneTimeToken
              args:
                enabled: true

过滤器配置跨域

spring:
    cloud:
        gateway:
            globalcors:
                cors-configurations:
                    '[/**]':
                        allowedOrigins: "https://"
                        alloweMethods:
                        - GET
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值