文章目录
一、介绍二、Gateway依赖三、 Gateway网关的yml配置自动转发配置跨域配置全局http超时配置route (路由)配置predicates (断言/谓词)配置PathQueryHostHeaderMethodWeightfilters(过滤器)配置PrefixPathStripPrefixLoadBalancerClient路由过滤器(客户端负载均衡)完整yml配置4.网关限流令牌桶算法使用令牌桶进行请求次数限流引入依赖定义KeyResolver修改application.yml中配置项测试三、Gateway网关+JWT鉴权流程分析JWTUtil工具类创建鉴权过滤器 验证token一、介绍
实现微服务网关的技术有很多。
nginx
Nginx(enginex)是一个高性能的HTTP和反向代理web服务器,同时也提供 了IMAP/POP3/SMTP服务zuul,
Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。spring-cloud-gateway,
Gateway是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。
我们使用gateway
这个网关技术,无缝衔接到基于spring cloud
的微服务开发中来。
Gateway官网:https://spring.io/projects/spring-cloud-gateway
Route(路由):
这是网关的基本构建块。它由一个ID、一个URI、一组断言和一组过滤器定义。如果断言为真,则路由匹配。Predicate(断言):
输入类型是一个 Serverwebexchange。我们可以使用它来匹配来自HTP请求的任何内容,例如 headers或参数。filter( 过滤器):
Gateway中的 Filter分为两种类型的 Filter,分别是GatewayFilter
和Global Filter
。过滤器 Filter将会对请求和响应进行修改处理。
二、Gateway依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
三、 Gateway网关的yml配置
自动转发配置
discovery:locator:#开启从注册中心动态创建路由的功能,网关自动映射处理逻辑:http://gatewayIP:gatewayPort/微服务名称/微服务请求地址enabled: false # 把网关请求自动转发到微服务请求地址: http://微服务名称/微服务请求地址lower-case-service-id: true #开启服务名称小写装换,Eureka 对服务名称默认大写管理# ========以上配置就已经实现请求转发的功能了============# 如 http://localhost:9003/springcloud-alibaba-account/account/findAll 自动转发到 http://springcloud-alibaba-account/account/findAll# 商业开发中,enable 一般不设置,使用默认值false ,避免不必要的自动转发规则
跨域配置
globalcors: # 跨域配置corsConfigurations:'[/**]':allowedHeaders: "*"allowedOrigins: "*"allowCredentials: trueallowedMethods:- GET- POST- DELETE- PUT- OPTION
全局http超时配置
httpclient: # 配置全局 http 超时connect-timeout: 1000 #必须以毫秒为单位指定response-timeout: 5s #必须指定为 java.time.Duration
route (路由)配置
predicates (断言/谓词)配置
PathRoutePredicateFactory
继承自AbstractRoutePredicateFactory
可以看到AbstractRoutePredicateFactory
有许多的实现类,这些类的前缀
就是我们谓词的参数,比如:Path、Header、Before、Query、Host、Method、Weight、Cookie、After、Between、Read等。
Path
- id: account # 路由定义的命名 唯一即可uri: http://localhost:9000#uri: ld://springcloud-alibaba-account/ # 当前路由定义对应的转发地址 lb:服务名称 使用LoadBalancerClient 实现负载均衡#配置谓词集合 RoutePredicateFactorypredicates:# 谓词名称是有套路的,是GatewayPredicate接口实现类的命名前缀 XxxRoutePredicateFactory- Path=/account/** #定义一个谓词 格式:谓词名字=参数 或者 name:名字 args:参数 # http://localhost:9004/account/account/findAll 转发到 http://localhost:9000/account/findAll# 配置过滤集合filters:- StripPrefix=1 # 将请求中的第一个路径去掉 请求路径以/区分,一个/表示一个路径,如:/api/good 会变成/good- id: baiduuri: predicates:# http://localhost:9004/bd/ 转发至 # 断言匹配到Path 转发 /bd 去掉/bd路径- Path=/bd/**filters:- StripPrefix=1
比如http://localhost:9004/account/account/findAll
就会被Path
断言匹配到,而转发到http://localhost:9000/account/account/findAll
,经过StripPrefix
会去除一个路径,最终变为:http://localhost:9000/account/findAll
http://localhost:9004/bd/
可以断言匹配转发到百度,而http://localhost:9004/bd/a
则转发失败,原因是断言后的转发路径是/a
。
Query
- id: queryuriuri: /predicates:# http://localhost:9004/?uri=qq 转发至 # http://localhost:9004/?uri=qq&url=baidu 也可以转发到 # http://localhost:9004/?url=baidu&uri=qq 也可以转发到 # 由此可见: 只要路径中有uri=qq 就可以匹配转发 ,且断言匹配是由上到下的# uri中匹配qq- Query=uri,qqmetadata: # 配置每条路由超时response-timeout: 200 # 必须以毫秒为单位指定。connect-timeout: 200 # 必须以毫秒为单位指定。- id: queryurluri: predicates:# http://localhost:9004/?url=baidu 转发至 # url 中匹配 baidu - Query=url,baidu- id: queaynameuri: predicates:# 参数name的值包含zs则会匹配成功- Query=name,zs.*
Host
Header
- id: headeruri: http://localhost:9000predicates:# http://localhost:9004/header/account/findAll 转发至 http://localhost:9000/account/findAll# 路径匹配 header ,请求头中必须myheader:zhangsan 并去除 一个路径 header 才能成功转发- Path=/header/**- name: Headerargs:# key: valueheader: myheaderregexp: zhangsan.*filters:- StripPrefix=1
如果没有断言的请求头信息,则不会被转发:
Method
- id: queaynameuri: predicates:# http://localhost:9004/?url=baidu 转发至 - Query=name,zs.*- Method=GET
使用Method属性指定方法的请求格式,如以上配置只允许GET请求:
如果修改为:- Method=GET,POST
,则允许GET和POST请求:
Weight
负载均衡中的权重,同一个组中URI进行负载均衡 语法: weight=组名,负载均衡权重,两个的路由的Path是一样的, 但是组名都是 orderGroup 其中80%被转发到order-one,20%被转发到 order-two。
一般用于多版本发布的时候。
- id: weight1uri: ld://order-onepredicates:-Path=/order/**# 负载均衡中的权重,同一个组中URI进行负载均衡 语法: weight=组名,负载均衡权重-Weight=orderGroup,8filters:- StripPrefix=1- - id: weight2uri: ld://order-twopredicates:-Path=/order/**# 两个的路由的Path是一样的, 但是组名都是 orderGroup 其中80%被转发到order-one,20%被转发到 order-two-Weight=orderGroup,2filters:- StripPrefix=1
filters(过滤器)配置
PrefixPath
-PrefixPath
添加一个路径
StripPrefix
请求路径以/区分 一个/代表一个路径
-StripPrefix
:去掉一个路径
LoadBalancerClient路由过滤器(客户端负载均衡)
完整yml配置
4.网关限流
当我们的系统被频繁的请求的时候,就有可能将系统压垮,所以为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。
令牌桶算法
令牌桶算法是比较常见的限流算法之ー,大概描述如下
(1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理
(2)根据限流大小,设置按照一定的速率往桶里添加令牌
(3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝
(4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接除
(5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流
使用令牌桶进行请求次数限流
引入依赖
spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要 使用首先需要引入redis的依赖
<!--redis 使用这个Redis依赖具有限流作用--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId><version>2.1.3.RELEASE</version></dependency>
定义KeyResolver
在GatewayApplicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。
/*** 创建一个ipKeyResolver 指定用户的IP* 创建用户唯一标识 使用IP作为用户唯一标识,根据IP来进行限流操作* @return*/@Bean(name="ipKeyResolver")public KeyResolver keyResolver(){return new KeyResolver() {@Overridepublic Mono<String> resolve(ServerWebExchange exchange) {//1.获取请求request对象ServerHttpRequest request = exchange.getRequest();//2.从request中获取ip地址String hostString = request.getRemoteAddress().getHostString();//Ip地址//3.返回return Mono.just(hostString);}};}
修改application.yml中配置项
指定限制流量的配置以及REDIS的配置。
解释:
burstCapacity
:令牌桶总容量。
replenishRate
:令牌桶每秒填充平均速率。
key-resolver
:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根 据#{@beanName}从 Spring 容器中获取 Bean 对象。通过在 replenishRate 和中设置相同的值来实现稳定的速率 burstCapacity 。设置 burstCapacity 高于时,可以允许临时突发 replenishRate 。在这种情况下,需要在 突发之间允许速率限制器一段时间(根据 replenishRate ),因为2次连续突发将导致请 求被丢弃( HTTP 429 ‐ Too Many Requests )
key-resolver
: “#{@userKeyResolver}” 用于通过SPEL表达式来指定使用哪一个 KeyResolver.
如上配置:
表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。
测试
启动redis
启动注册中心
启动商品微服务
启动gateway网关
打开浏览器 http://localhost:9101/goods/brand 快速刷新,当1秒内发送多次请求,就会返回429错误。
三、Gateway网关+JWT鉴权流程分析
如果不了解Jwt机制,可以点击了解一下:Jwt鉴权
服务鉴权流程:
用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
JWTUtil工具类
@Data@Component@ConfigurationProperties(prefix ="jwt.config")public class JwtUtil {private String key ;private long ttl ;/*** 生成JWT Token*/public String createJWT(String id, String subject, String roles,String permission) {long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject).setIssuedAt(now).signWith(SignatureAlgorithm.HS256, key).claim("roles", roles).claim("permission",permission);if (ttl > 0) {builder.setExpiration( new Date( nowMillis + ttl));}return pact();}/*** 解析JWT*/public Claims parseJWT(String jwtStr){return Jwts.parser().setSigningKey(key).parseClaimsJws(jwtStr).getBody();}}
创建鉴权过滤器 验证token
/*** @author :LiuShihao* @date :Created in /11/13 10:05 上午* @desc :GlobalFilter是Gateway网关提供的拦截器*/@Componentpublic class AuthorizationFilter implements GlobalFilter, Ordered {@AutowiredJwtUtil jwtUtil;/*** 令牌的名字*/private static final String AUTHORIZE_TOKEN="Authorization";/*** 全局拦截* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();// 获取用户令牌信息//1.请求头中 获取请求头中的第一个参数String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);// 定义一个布尔值 为true说明令牌信息在请求头中 为false则不在Boolean hasToken = true;//2.如果请求头中没有令牌信息就从参数中获取if (StringUtils.isEmpty(token)){//表示从所有的请求参数中获取第一个token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);hasToken = false;}//3.Cookie中if (StringUtils.isEmpty(token)){//如果请求参数中也没有令牌信息就从Cookie中获取HttpCookie cookie = request.getCookies().getFirst(AUTHORIZE_TOKEN);if (cookie != null){token = cookie.getValue();}}// 如果没有令牌信息 则拦截if (StringUtils.isEmpty(token)){//无效 拦截//设置没有权限的状态码 401response.setStatusCode(HttpStatus.UNAUTHORIZED);//响应空数据return response.setComplete();}// 如果有令牌信息 则校验是否有效try{jwtUtil.parseJWT(token);}catch (Exception e){//无效 拦截//设置没有权限的状态码 401response.setStatusCode(HttpStatus.UNAUTHORIZED);//响应空数据return response.setComplete();}//有效 放行//并且将令牌信息放到请求头中request.mutate().header(AUTHORIZE_TOKEN,token);return chain.filter(exchange);}/*** 排序* @return*/@Overridepublic int getOrder() {return 0;}}
如果觉得《SpringCloud微服务网关技术——Gateway网关的使用》对你有帮助,请点赞、收藏,并留下你的观点哦!