第06章 - 网关服务详解

第06章 - 网关服务详解

6.1 网关概述

6.1.1 网关的作用

Spring Cloud Gateway是RuoYi-Cloud的统一入口,承担着微服务架构中的核心职责:

                                    外部请求
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Spring Cloud Gateway                                │
│                            (ruoyi-gateway)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                           核心功能                                   │    │
│  ├───────────────┬───────────────┬───────────────┬───────────────────┤    │
│  │   路由转发    │   负载均衡    │   协议转换    │   服务发现        │    │
│  └───────────────┴───────────────┴───────────────┴───────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                           安全功能                                   │    │
│  ├───────────────┬───────────────┬───────────────┬───────────────────┤    │
│  │   身份认证    │   权限校验    │   XSS过滤     │   黑名单过滤      │    │
│  └───────────────┴───────────────┴───────────────┴───────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                           流量控制                                   │    │
│  ├───────────────┬───────────────┬───────────────┬───────────────────┤    │
│  │   限流控制    │   熔断降级    │   请求聚合    │   灰度发布        │    │
│  └───────────────┴───────────────┴───────────────┴───────────────────┘    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                        │
        ┌───────────────────────────────┼───────────────────────────────┐
        │                               │                               │
        ▼                               ▼                               ▼
┌───────────────┐            ┌───────────────┐            ┌───────────────┐
│  ruoyi-auth   │            │ ruoyi-system  │            │  ruoyi-gen    │
└───────────────┘            └───────────────┘            └───────────────┘

6.1.2 项目结构

ruoyi-gateway/
├── src/main/java/com/ruoyi/gateway/
│   ├── RuoYiGatewayApplication.java     # 启动类
│   ├── config/
│   │   ├── GatewayConfig.java           # 网关配置
│   │   ├── RouterFunctionConfiguration.java
│   │   ├── SwaggerProvider.java         # Swagger聚合配置
│   │   └── properties/
│   │       ├── CaptchaProperties.java   # 验证码配置
│   │       ├── IgnoreWhiteProperties.java # 白名单配置
│   │       └── XssProperties.java       # XSS配置
│   ├── filter/
│   │   ├── AuthFilter.java              # 认证过滤器
│   │   ├── BlackListUrlFilter.java      # 黑名单过滤器
│   │   ├── CacheRequestFilter.java      # 请求缓存过滤器
│   │   ├── ValidateCodeFilter.java      # 验证码过滤器
│   │   └── XssFilter.java               # XSS过滤器
│   ├── handler/
│   │   ├── GatewayExceptionHandler.java # 异常处理器
│   │   ├── SentinelFallbackHandler.java # Sentinel降级处理
│   │   └── ValidateCodeHandler.java     # 验证码处理器
│   └── service/
│       └── ValidateCodeService.java     # 验证码服务
└── src/main/resources/
    └── bootstrap.yml                    # 启动配置

6.2 路由配置

6.2.1 路由配置详解

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能
          enabled: true
          # 服务名小写
          lower-case-service-id: true
      routes:
        # 认证中心
        - id: ruoyi-auth
          uri: lb://ruoyi-auth
          predicates:
            - Path=/auth/**
          filters:
            - CacheRequestFilter
            - ValidateCodeFilter
            - StripPrefix=1
            
        # 代码生成
        - id: ruoyi-gen
          uri: lb://ruoyi-gen
          predicates:
            - Path=/code/**
          filters:
            - StripPrefix=1
            
        # 定时任务
        - id: ruoyi-job
          uri: lb://ruoyi-job
          predicates:
            - Path=/schedule/**
          filters:
            - StripPrefix=1
            
        # 系统模块
        - id: ruoyi-system
          uri: lb://ruoyi-system
          predicates:
            - Path=/system/**
          filters:
            - StripPrefix=1
            
        # 文件服务
        - id: ruoyi-file
          uri: lb://ruoyi-file
          predicates:
            - Path=/file/**
          filters:
            - StripPrefix=1

6.2.2 路由核心概念

Route(路由)

路由是网关的基本构建块,由ID、目标URI、谓词集合和过滤器集合组成。

Predicate(谓词)

用于匹配HTTP请求的条件,常用谓词:

谓词类型 说明 示例
Path 路径匹配 Path=/api/**
Method HTTP方法匹配 Method=GET,POST
Header 请求头匹配 Header=X-Request-Id,\d+
Query 查询参数匹配 Query=name,zhangsan
Cookie Cookie匹配 Cookie=sessionId,abc
Host 主机名匹配 Host=**.ruoyi.vip
Before 时间之前 Before=2023-12-31T23:59:59
After 时间之后 After=2023-01-01T00:00:00
Between 时间区间 Between=2023-01-01,2023-12-31
Weight 权重路由 Weight=group1,8

Filter(过滤器)

过滤器允许以某种方式修改传入的HTTP请求或返回的HTTP响应。

filters:
  # 路径前缀去除
  - StripPrefix=1
  # 路径添加前缀
  - PrefixPath=/api
  # 添加请求头
  - AddRequestHeader=X-Request-red, blue
  # 添加响应头
  - AddResponseHeader=X-Response-Red, Blue
  # 请求重试
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY
  # 限流
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20

6.3 过滤器详解

6.3.1 认证过滤器 AuthFilter

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
    
    // 排除过滤的 uri 地址,nacos自行配置
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;
    
    @Autowired
    private RedisService redisService;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();
        
        String url = request.getURI().getPath();
        
        // 跳过不需要验证的路径
        if (StringUtils.matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }
        
        String token = getToken(request);
        
        if (StringUtils.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }
        
        String userkey = JwtUtils.getUserKey(claims);
        boolean islogin = redisService.hasKey(getTokenKey(userkey));
        if (!islogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        
        String userid = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
        
        if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        
        // 设置用户信息到请求
        addHeader(mutate, SecurityConstants.USER_KEY, userkey);
        addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
        addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
        
        // 内部请求来源参数清除
        removeHeader(mutate, SecurityConstants.FROM_SOURCE);
        
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }
    
    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }
    
    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }
    
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }
    
    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }
    
    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }
    
    @Override
    public int getOrder() {
        return -200;
    }
}

6.3.2 验证码过滤器 ValidateCodeFilter

@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
    
    private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
    
    @Autowired
    private ValidateCodeService validateCodeService;
    
    @Autowired
    private CaptchaProperties captchaProperties;
    
    private static final String CODE = "code";
    private static final String UUID = "uuid";
    
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            
            // 非登录/注册请求或验证码关闭,不处理
            if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) 
                    || !captchaProperties.getEnabled()) {
                return chain.filter(exchange);
            }
            
            try {
                String rspStr = resolveBodyFromRequest(request);
                JSONObject obj = JSON.parseObject(rspStr);
                validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
            } catch (Exception e) {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
            }
            return chain.filter(exchange);
        };
    }
    
    private String resolveBodyFromRequest(ServerHttpRequest request) {
        // 获取请求体中的内容
        Flux<DataBuffer> body = request.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        return bodyRef.get();
    }
}

6.3.3 XSS过滤器 XssFilter

@Component
@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true")
public class XssFilter implements GlobalFilter, Ordered {
    
    @Autowired
    private XssProperties xss;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // GET DELETE 不过滤
        HttpMethod method = request.getMethod();
        if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
            return chain.filter(exchange);
        }
        
        // 非json类型,不过滤
        if (!isJsonRequest(exchange)) {
            return chain.filter(exchange);
        }
        
        // excludeUrls 不过滤
        String url = request.getURI().getPath();
        if (StringUtils.matches(url, xss.getExcludeUrls())) {
            return chain.filter(exchange);
        }
        
        ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange);
        return chain.filter(exchange.mutate().request(httpRequestDecorator).build());
    }
    
    private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) {
        ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> body = super.getBody();
                return body.buffer().map(dataBuffers -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer join = dataBufferFactory.join(dataBuffers);
                    byte[] content = new byte[join.readableByteCount()];
                    join.read(content);
                    DataBufferUtils.release(join);
                    String bodyStr = new String(content, StandardCharsets.UTF_8);
                    // XSS过滤
                    bodyStr = EscapeUtil.clean(bodyStr);
                    // 转成字节
                    byte[] bytes = bodyStr.getBytes();
                    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(
                            ByteBufAllocator.DEFAULT);
                    DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
                    buffer.write(bytes);
                    return buffer;
                });
            }
            
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }
        };
        return serverHttpRequestDecorator;
    }
    
    /**
     * 是否是Json请求
     */
    public boolean isJsonRequest(ServerWebExchange exchange) {
        String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        if (StringUtils.isNotEmpty(contentType)) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
        }
        return false;
    }
    
    @Override
    public int getOrder() {
        return -100;
    }
}

6.3.4 黑名单过滤器 BlackListUrlFilter

@Component
public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config> {
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String url = exchange.getRequest().getURI().getPath();
            if (config.matchBlacklist(url)) {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), 
                    "请求地址不允许访问");
            }
            return chain.filter(exchange);
        };
    }
    
    public BlackListUrlFilter() {
        super(Config.class);
    }
    
    public static class Config {
        private List<String> blacklistUrl;
        private List<Pattern> blacklistUrlPattern = new ArrayList<>();
        
        public boolean matchBlacklist(String url) {
            return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream()
                .anyMatch(p -> p.matcher(url).find());
        }
        
        public List<String> getBlacklistUrl() {
            return blacklistUrl;
        }
        
        public void setBlacklistUrl(List<String> blacklistUrl) {
            this.blacklistUrl = blacklistUrl;
            this.blacklistUrlPattern.clear();
            this.blacklistUrl.forEach(url -> {
                this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), 
                    Pattern.CASE_INSENSITIVE));
            });
        }
    }
}

6.3.5 请求缓存过滤器 CacheRequestFilter

@Component
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config> {
    
    public CacheRequestFilter() {
        super(Config.class);
    }
    
    @Override
    public String name() {
        return "CacheRequestFilter";
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter();
        Integer order = config.getOrder();
        if (order == null) {
            return cacheRequestGatewayFilter;
        }
        return new OrderedGatewayFilter(cacheRequestGatewayFilter, order);
    }
    
    public static class CacheRequestGatewayFilter implements GatewayFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // GET DELETE 不过滤
            HttpMethod method = exchange.getRequest().getMethod();
            if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
                return chain.filter(exchange);
            }
            return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
                if (serverHttpRequest == exchange.getRequest()) {
                    return chain.filter(exchange);
                }
                return chain.filter(exchange.mutate().request(serverHttpRequest).build());
            });
        }
    }
    
    static public class Config {
        private Integer order;
        
        public Integer getOrder() {
            return order;
        }
        
        public void setOrder(Integer order) {
            this.order = order;
        }
    }
}

6.4 Sentinel限流

6.4.1 Sentinel配置

spring:
  cloud:
    sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        # 控制台地址
        dashboard: localhost:8718
      # nacos配置持久化
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: sentinel-ruoyi-gateway
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow

6.4.2 Sentinel规则配置

[
  {
    "resource": "ruoyi-auth",
    "count": 500,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  },
  {
    "resource": "ruoyi-system",
    "count": 1000,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  }
]

6.4.3 Sentinel降级处理

@Component
public class SentinelFallbackHandler implements WebExceptionHandler {
    
    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍后再试");
    }
    
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex)) {
            return Mono.error(ex);
        }
        return handleBlockedRequest(exchange, ex)
            .flatMap(response -> writeResponse(response, exchange));
    }
    
    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}

6.5 跨域配置

6.5.1 全局跨域配置

@Configuration
public class GatewayConfig {
    
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        
        return new CorsWebFilter(source);
    }
}

6.5.2 Nacos配置跨域

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowed-methods: "*"
            allowed-headers: "*"
            allow-credentials: true
            exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"

6.6 异常处理

6.6.1 全局异常处理器

@Order(-1)
@Configuration
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
    
    private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
    
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        
        String msg;
        
        if (ex instanceof NotFoundException) {
            msg = "服务未找到";
        } else if (ex instanceof ResponseStatusException) {
            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
            msg = responseStatusException.getMessage();
        } else {
            msg = "内部服务器错误";
        }
        
        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
        
        return ServletUtils.webFluxResponseWriter(response, msg);
    }
}

6.6.2 响应工具类

public class ServletUtils {
    
    /**
     * WebFlux响应结果
     */
    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg) {
        return webFluxResponseWriter(response, msg, HttpStatus.OK);
    }
    
    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, HttpStatus status) {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }
    
    /**
     * URL编码
     */
    public static String urlEncode(String str) {
        try {
            return URLEncoder.encode(str, Constants.UTF8);
        } catch (UnsupportedEncodingException e) {
            return StringUtils.EMPTY;
        }
    }
}

6.7 Swagger聚合

6.7.1 Swagger配置

@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
    
    /**
     * Swagger3默认的url后缀
     */
    public static final String SWAGGER3URL = "/v3/api-docs";
    
    @Autowired
    private RouteLocator routeLocator;
    
    @Autowired
    private GatewayProperties gatewayProperties;
    
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        gatewayProperties.getRoutes().stream()
            .filter(routeDefinition -> routes.contains(routeDefinition.getId()))
            .forEach(route -> {
                route.getPredicates().stream()
                    .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
                    .forEach(predicateDefinition -> resources.add(
                        swaggerResource(route.getId(), 
                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                .replace("/**", SWAGGER3URL))
                    ));
            });
        return resources;
    }
    
    private SwaggerResource swaggerResource(String name, String url) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setUrl(url);
        swaggerResource.setSwaggerVersion("3.0");
        return swaggerResource;
    }
}

6.7.2 Swagger路由

@Configuration
public class RouterFunctionConfiguration {
    
    @Autowired(required = false)
    private SwaggerProvider swaggerProvider;
    
    @Bean
    public RouterFunction routerFunction() {
        return RouterFunctions.route(
            RequestPredicates.GET("/swagger-resources")
                .and(RequestPredicates.accept(MediaType.ALL)),
            request -> ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(swaggerProvider.get()))
        );
    }
}

6.8 配置属性类

6.8.1 白名单配置

@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {
    
    /**
     * 放行白名单配置,网关不校验此处的白名单
     */
    private List<String> whites = new ArrayList<>();
    
    public List<String> getWhites() {
        return whites;
    }
    
    public void setWhites(List<String> whites) {
        this.whites = whites;
    }
}

6.8.2 验证码配置

@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties {
    
    /**
     * 验证码开关
     */
    private Boolean enabled;
    
    /**
     * 验证码类型(math 数字计算 char 字符验证)
     */
    private String type;
    
    // getter/setter...
}

6.8.3 XSS配置

@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.xss")
public class XssProperties {
    
    /**
     * Xss开关
     */
    private Boolean enabled;
    
    /**
     * 排除路径
     */
    private List<String> excludeUrls = new ArrayList<>();
    
    // getter/setter...
}

6.9 网关最佳实践

6.9.1 性能优化建议

  1. 合理设置超时时间
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 10000
        response-timeout: 30000
  1. 启用响应压缩
server:
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/xml,text/plain
    min-response-size: 2048
  1. 连接池优化
spring:
  cloud:
    gateway:
      httpclient:
        pool:
          max-connections: 500
          max-idle-time: 20000

6.9.2 安全建议

  1. 启用XSS过滤
  2. 配置合理的白名单
  3. 使用HTTPS
  4. 配置请求限流
  5. 开启访问日志

6.9.3 监控建议

  1. 集成Prometheus监控
  2. 配置健康检查端点
  3. 开启请求追踪
  4. 配置告警规则

6.10 小结

本章详细介绍了RuoYi-Cloud的网关服务,包括:

  1. 网关职责:路由转发、负载均衡、安全认证
  2. 路由配置:谓词、过滤器的使用
  3. 过滤器:认证、验证码、XSS、黑名单过滤器
  4. Sentinel限流:流量控制和降级处理
  5. 跨域配置:CORS处理
  6. 异常处理:全局异常处理机制
  7. Swagger聚合:API文档聚合

网关是微服务架构的核心组件,理解其工作原理对于系统开发和运维至关重要。


上一章:认证与授权中心 | 返回目录 | 下一章:系统管理模块

posted @ 2026-01-08 14:05  我才是银古  阅读(9)  评论(0)    收藏  举报