本文主要是介绍Spring Boot 3 整合 Spring Cloud Gateway实践过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《SpringBoot3整合SpringCloudGateway实践过程》本文介绍了如何使用SpringCloudAlibaba2023.0.0.0版本构建一个微服务网关,包括统一路由、限...
引子
当前微服务架构已成为中大型系统的标配,但在享受拆分带来的敏捷性时,流量治理与安全管控的复杂度也呈指数级上升。因此,我们需要构建微服务网关来为系统“保驾护航”。本文将会通过一个项目(核心模块包含 鉴权服务、文件服务、主服务 共 3 个微服务),采用 Spring Cloud Alibaba 2023.0.0.0 版本技术栈(核心组件:Nacos 2.5.0 注册中心与配置中心),分享如何构建一个微服务网关。
为什么需要微服务网关
我们当前模拟的这个项目中包含了三个业务服务,如果部署到线上的话,每个服务都有自己的ip(或域名)以及端口号。因此,我们的业务入口是分散的且暴露在外的,我们无法统一拦截异常流量以及限制接口访问等。但有了微服务网关,我们就可以将所有的请求都先集中在网关这里(有点类似于一个房子的大门口),由网关对所有请求进行统一的管理。
实践
在知晓了网关的作用后,我们将实践如何在一个现成的微服务项目中整合gateway网关以及做功能开发。当然,在这之前,我们需要先完成整合。首先,我们需要建一个网关模块,如下:
完成模块的创建后,导入gateway相关的依赖,如下:
<dependencies> <dependency> <groupId>com.pitayafruits</groupId> <artifactId>wechat-pojo</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
说明一下:这里引入的pojo
包含了项目中常用的方法、工具类等;web
则是因为网关本身也是一个可以访问的服务,所以需要引入;gateway
则是这里需要使用的网关的依赖。然后来对它进行基础的配置,如下:
server:
port: 1000
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # 不限制请求体大小
spring:
application:
name: gateway
cloudfDjXzZhdwc:
nacos:
config:
server-addr: 127.0.0.1:8848
username: nacos
password: naocs
# 日志级别
logging:
level:
root: info
我们使用了nacos
来管理服务,网关自然也是一个服务,因此也需要把它注册到nacos
。
1.统一路由
引入网关的首要作用是统一访问的入口,所有的服务访问都要先经过网关。因此,第一个要实现的功能就是统一路由。而它的实现也是非常简单,只需要在配置文件中做下简单配置即可:
spring: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: # 路由配置信息(数组/list) - id: authRoute # 每项路由规则都有一个唯一的id编号,可以自定义 uri: lb://auth-service # lb=负载均衡,会动态寻址 predicates: - Path=/a/** - id: fileRoute uri: lb://file-service predicates: - Path=/f/** - id: mainRoute uri: lb://main-service predicates: - Path=/m/** globalcors: # 允许跨域的相关配置 cors-configurations: '[/**]': allowedOriginPatterns: "*" allowedHeaders: "*" allowedMethods: "*" allowCredentials: true
这里对routes
下的相关配置说明下:id
是给每个服务的路由一个唯一编号,保证唯一即可,通常我们采用的写法python是服务名+route
;uri则是服务名称,如果写成ip或者域名,那么地址发生变化,我们还需要重新修改配置,但是服务名称是可以固定不变的;接下来是predicates
,它可以配置多个值,我们一个服务里会有多个controller
,把每个controller
的路由配置在这里即可,/**
表示指定的controller
下的所有方法。
另外,如果负载均衡这个写法无法被识别,说明你当前使用的spring-cloud
版本中默认并不包含相关依赖,我们需要手动引入它。
<dependency> <groupId>org.spChina编程ringframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
完成上述配置后,我们此时其他服务的API将无法直接访问,而统一通过网关来访问。例如原本main-service
中的127.0.0.1:88/m/hello
变成了 127.0.0.1:1000/m/hello
。
2.限流防刷
提到网关,一个绕不开的话题就是限流。如果有人恶意刷我们的接口,我们就需要对某些IP进行访问限制,比如在XX秒内访问同一接口超过XX次,就需要限制访问。它的实现非常简单,声明一个处理类继承gateway的相关过滤接口即可。代码如下:
@Component public class IPLimitFilter implements GlobalFilter { private static final Integer continueCounts = 3; private static final Integer timeInterval = 20; private static final Integer limitTimes = 30; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return doLimit(exchange, chain); } /** * 限制ip请求次数的判断 * * @param exchange 请求交换器 * @param chain 过滤器链 * @return 返回值 */ public Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取ip ServerHttpRequest request = exchange.getRequest(); String ip = IPUtil.getIP(request); // 正常ip定义 final String ipRedisKey = "gateway-ip" + ip; // 被拦截的黑名单,如果在redis中javascript存在,那么就不允许访问 final String ipRedisLimitKey = "gateway-ip:limit" + ip; // 判断当前ip的剩余时间,如果大于0,则表示还处于黑名单 long limitLeftTimes = redis.ttl(ipRedisLimitKey); if ( limitLeftTimes > 0 ) { return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP); } // 在redis中更新次数 long requestCounts = redis.increment(ipRedisKey, 1); // 如果第一次访问,就需要设置间隔时间 if (requestCounts == 1) { redis.expire(ipRedisKey, timeInterval); } // 如果还能获得正常请求次数,说明用户的正常请求落在正常时间内,超过则限制 if (requestCounts > continueCounts) { redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes); return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP); } // 放行请求 return chain.filter(exchange); } //过滤器的顺序,数字越小优先级越大. @Override public int getOrder() { return 1; }
我们需要借助redis来实现根据时间对指定ip的控制,这里的逻辑是:如果某个ip在30秒访问超过三次,就限制访问,如果限制了,则20秒后再恢复。
3.登录鉴权
关于登录鉴权,我们目前通常会采用无状态
的做法:即用户登录后,后端返回token给前端,前端后续所有的请求都在headers
中携带token,后端服务不存储token,只对前端发来的token进行校验和解析。而网关作为所有服务的入口,自然而然地也就可以承担起这个职责了。
import com.google.gson.Gson; import com.pitayafruits.base.BaseInfoProperties; import com.pitayafruits.grace.result.GracejsONResult; import com.pitayafruits.grace.result.ResponseStatusEnum; import jakarta.annotation.Resource; www.chinasem.cnimport lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.util.MimeTypeUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import Java.nio.charset.StandardCharsets; import java.util.List; @Component @Slf4j @RefreshScope public class SecurityFilterToken extends BaseInfoProperties implements GlobalFilter, Ordered { @Resource private ExcludeUrlProperties excludeUrlProperties; private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取用户请求路径 String url = exchange.getRequest().getURI().getPath(); // 获取所有需要排除校验的url List<String> excludeList = excludeUrlProperties.getUrls(); // 校验并排除url if (excludeList != null && !excludeList.isEmpty()) { for (String excludeUrl : excludeList) { if (antPathMatcher.matchStart(excludeUrl, url)) { return chain.filter(exchange); } } } // 从header中获得用户id和token String userId = exchange.getRequest().getHeaders().getFirst(HEADER_USER_ID); String userToken = exchange.getRequest().getHeaders().getFirst(HEADER_USER_TOKEN); // 校验header中的token if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) { String redisToken = redis.get(REDIS_USER_TOKEN + ":" + userId); if (redisToken.equals(userToken)) { return chain.filter(exchange); } } // 默认不放行 return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN); } //过滤器的顺序,数字越小优先级越大. @Override public int getOrder() { return 0; } /** * 异常信息包装 * * @param exchange 交换器 * @param statusEnum 状态枚举 * @return 返回值 */ public Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum statusEnum) { //1.获得response ServerHttpResponse response = exchange.getResponse(); //2.构建jsonResult GraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum); //3.设置header类型 if (!response.getHeaders().containsKey("Content-Type")) { response.getHeaders().add("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE); } //4.设置状态码 response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); //5.转换json并向response写数据 String resultJson = new Gson().toJson(jsonResult); DataBuffer buffer = response.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8)); //6.返回 return response.writeWith(Mono.just(buffer)); } }
在我这个示例中,我做的校验逻辑很简单:只是用户登录的时候会在redis
里存放生成的token
,然后其他接口访问的时候比对下传来的token
和redis
里存放的token
是否一致。这里需要关注下过滤器的顺序,目前的案例中我们已经编写了两个过滤器-限流防刷和登录鉴权。所以可以把登录鉴权过滤器的执行顺序改为0,限流防抖改为1。
另外,我们需要对部分接口放行不拦截,比如登录接口。而我这里的做法则是将放行接口写在配置文件里,并声明配置类进行读取。
exclude.urls[0] = /passport/getSMSCode exclude.urls[1] = /passport/regist exclude.urls[2] = /passport/login
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.List; @Component @Data @PropertySource("classpath:excludeUrlPath.properties") @ConfigurationProperties(prefix = "exclude") public class ExcludeUrlProperties { private List<String> urls; }
特别说明下:这里制定好过滤器的执行顺序后,内部的验证逻辑根据自己实际情况填写,我这里没用鉴权框架只是方便讲解,要用也很简单,引入之后把相关的鉴权逻辑写进对应的过滤器就行。
小结
在本文中,我们完成了Spring Cloud Gateway
微服务网关的整合,并完成了三个最基础常见的实践场景。如果你的项目有更多的业务需求,只需要加相应的过滤器并制定好过滤器的执行顺序即可,希望对大家有所帮助!
到此这篇关于Spring Boot 3 整合 Spring Cloud Gateway实践过程的文章就介绍到这了,更多相关Spring Boot 整合 Spring Cloud Gateway 内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!
这篇关于Spring Boot 3 整合 Spring Cloud Gateway实践过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!