当前位置:网站首页>Our company has used gateway services for 6 years, dynamic routing, authentication, current limiting, etc., a stable batch!
Our company has used gateway services for 6 years, dynamic routing, authentication, current limiting, etc., a stable batch!
2022-07-30 15:13:00 【Feifei Technology House】
前言
本文记录一下我是如何使用Gateway搭建网关服务及实现动态路由的,帮助大家学习如何快速搭建一个网关服务,了解路由相关配置,鉴权的流程及业务处理,有兴趣的一定看到最后,非常适合没接触过网关服务的同学当作入门教程.
搭建服务
框架
SpringBoot 2.1
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
Spring-cloud-gateway-core
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
</dependency>
common-lang3
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
路由配置
网关作为请求统一入口,路由就相当于是每个业务系统的入口,通过路由规则则可以匹配到对应微服务的入口,将请求命中到对应的业务系统中
server:
port: 8080
spring:
cloud:
gateway:
enabled: true
routes:
- id: demo-server
uri: http://localhost:8081
predicates:
- Path=/demo-server/**
filters:
- StripPrefix= 1
routes
解读配置
现在有一个服务demo-server部署在本机,地址和端口为
127.0.0.1:8081,所以路由配置uri为http://localhost:8081使用网关服务路由到此服务,
predicates -Path=/demo-server/**,网关服务的端口为8080,启动网关服务,访问localhost:8080/demo-server,路由断言就会将请求路由到demo-server直接访问demo-server的接口
localhost:8081/api/test,通过网关的访问地址则为localhost:8080/demo-server/api/test,predicates配置将请求断言到此路由,filters-StripPrefix=1代表将地址中/后的第一个截取,所以demo-server就截取掉了
使用gateway通过配置文件即可完成路由的配置,非常方便,我们只要充分的了解配置项的含义及规则就可以了;但是这些配置如果要修改则需要重启服务,重启网关服务会导致整个系统不可用,这一点是无法接受的,下面介绍如何通过Nacos实现动态路由
动态路由
使用nacos结合gateway-server实现动态路由,我们需要先部署一个nacos服务,可以使用docker部署或下载源码在本地启动,具体操作可以参考官方文档即可
Nacos配置
groupId: 使用网关服务名称即可
dataId: routes
配置格式:json
[{
"id": "xxx-server",
"order": 1, #优先级
"predicates": [{ #路由断言
"args": {
"pattern": "/xxx-server/**"
},
"name": "Path"
}],
"filters":[{ #过滤规则
"args": {
"parts": 0 #k8s服务内部访问容器为http://xxx-server/xxx-server的话,配置0即可
},
"name": "StripPrefix" #截取的开始索引
}],
"uri": "http://localhost:8080/xxx-server" #目标地址
}]
json格式配置项与yaml中对应,需要了解配置在json中的写法
比对一下json配置与yaml配置
{
"id":"demo-server",
"predicates":[
{
"args":{
"pattern":"/demo-server/**"
},
"name":"Path"
}
],
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"http://localhost:8081"
}
spring:
cloud:
gateway:
enabled: true
routes:
- id: demo-server
uri: http://localhost:8081
predicates:
- Path=/demo-server/**
filters:
- StripPrefix= 1
代码实现
Nacos实现动态路由的方式核心就是通过Nacos配置监听,配置发生改变后执行网关相关api创建路由
@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware {
private static final Logger LOGGER = LoggerFactory.getLogger(NacosDynamicRouteService.class);
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
/** 路由id */
private static List<String> routeIds = Lists.newArrayList();
/**
* 监听nacos路由配置,动态改变路由
* @param configInfo
*/
@NacosConfigListener(dataId = "routes", groupId = "gateway-server")
public void routeConfigListener(String configInfo) {
clearRoute();
try {
List<RouteDefinition> gatewayRouteDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
publish();
LOGGER.info("Dynamic Routing Publish Success");
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* 清空路由
*/
private void clearRoute() {
for (String id : routeIds) {
routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
routeIds.clear();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 添加路由
*
* @param definition
*/
private void addRoute(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
routeIds.add(definition.getId());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* 发布路由、使路由生效
*/
private void publish() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
}
过滤器
gateway提供GlobalFilter及Ordered两个接口用来定义过滤器,我们自定义过滤器只需要实现这个两个接口即可
GlobalFilter filter()实现过滤器业务Ordered getOrder()定义过滤器执行顺序
通常一个网关服务的过滤主要包含 鉴权(是否登录、是否黑名单、是否免登录接口...) 限流(ip限流等等)功能,我们今天简单介绍鉴权过滤器的流程实现
鉴权过滤器
需要实现鉴权过滤器,我们先得了解登录及鉴权流程,如下图所示

由图可知,我们鉴权过滤核心就是验证token是否有效,所以我们网关服务需要与业务系统在同一个redis库,先给网关添加redis依赖及配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
spring:
redis:
host: redis-server
port: 6379
password:
database: 0
代码实现
定义过滤器AuthFilter
获取请求对象 从请求头或参数或cookie中获取token(支持多种方式传token对于客户端更加友好,比如部分web下载请求会新建一个页面,在请求头中传token处理起来比较麻烦)
没有token,返回401
有token,查询redis是否有效
无效则返回401,有效则完成验证放行
重置token过期时间、添加内部请求头信息方便业务系统权限处理
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String TOKEN_HEADER_KEY = "auth_token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求对象
ServerHttpRequest request = exchange.getRequest();
// 2.获取token
String token = getToken(request);
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isBlank(token)) {
// 3.token为空 返回401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 4.验证token是否有效
String userId = getUserIdByToken(token);
if (StringUtils.isBlank(userId)) {
// 5.token无效 返回401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// token有效,后续业务处理
// 从写请求头,方便业务系统从请求头获取用户id进行权限相关处理
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
request = builder.header("user_id", userId).build();
// 延长缓存过期时间-token缓存用户如果一直在操作就会一直重置过期
// 这样避免用户操作过程中突然过期影响业务操作及体验,只有用户操作间隔时间大于缓存过期时间才会过期
resetTokenExpirationTime(token, userId);
// 完成验证
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 优先级 越小越优先
return 0;
}
/**
* 从redis中获取用户id
* 在登录操作时候 登陆成功会生成一个token, redis得key为auth_token:token 值为用户id
*
* @param token
* @return
*/
private String getUserIdByToken(String token) {
String redisKey = String.join(":", "auth_token", token);
return redisTemplate.opsForValue().get(redisKey);
}
/**
* 重置token过期时间
*
* @param token
* @param userId
*/
private void resetTokenExpirationTime(String token, String userId) {
String redisKey = String.join(":", "auth_token", token);
redisTemplate.opsForValue().set(redisKey, userId, 2, TimeUnit.HOURS);
}
/**
* 获取token
*
* @param request
* @return
*/
private static String getToken(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
// 从请求头获取token
String token = headers.getFirst(TOKEN_HEADER_KEY);
if (StringUtils.isBlank(token)) {
// 请求头无token则从url获取token
token = request.getQueryParams().getFirst(TOKEN_HEADER_KEY);
}
if (StringUtils.isBlank(token)) {
// 请求头和url都没有token则从cookies获取
HttpCookie cookie = request.getCookies().getFirst(TOKEN_HEADER_KEY);
if (cookie != null) {
token = cookie.getValue();
}
}
return token;
}
}
总结
Gateway通过配置项可以实现路由功能,整合Nacos及配置监听可以实现动态路由,实现GlobalFilter, Ordered两个接口可以快速实现一个过滤器,文中也详细的介绍了登录后的请求鉴权流程,如果有不清楚地方可以评论区见咯.
边栏推荐
- The evolution of content products has three axes: traffic, technology, and product form
- ToDesk版本更新,引入RTC传输技术,是否早以替代向日葵远程控制?
- Flask框架——Sijax
- Conversion between pytorch and keras (the code takes LeNet-5 as an example)
- The use and principle of distributed current limiting reduction RRateLimiter
- PyQt5快速开发与实战 9.1 使用PyInstaller打包项目生成exe文件
- Understand Chisel language. 28. Chisel advanced finite state machine (2) - Mealy state machine and comparison with Moore state machine
- JUC common thread pool source learning 02 ( ThreadPoolExecutor thread pool )
- ECCV 2022 | Towards Data Efficient Transformer Object Detectors
- LeetCode_98_验证二叉搜索树
猜你喜欢

The highest level of wiring in the computer room, the beauty is suffocating

MongoDB启动报错 Process: 29784 ExecStart=/usr/bin/mongod $OPTIONS (code=exited, status=14)

关于容器的小案例

深入浅出零钱兑换问题——背包问题的套壳

Container sorting case

【回归预测-lssvm分类】基于最小二乘支持向量机lssvm实现数据分类代码

This editor actually claims to be as fast as lightning!

MongoDB starts an error Process: 29784 ExecStart=/usr/bin/mongod $OPTIONS (code=exited, status=14)

LeetCode_98_验证二叉搜索树

机房布线的至高境界,美到窒息
随机推荐
SSE for Web Message Push
A new generation of open source free terminal tools, so cool
我为何从开发人员转做测试,3年软件测试工程师,带你聊聊这其中的秘辛
智能合约安全——私有数据访问
CS内网横向移动 模拟渗透实操 超详细
华为无线设备Mesh配置命令
关于容器的小案例
Huawei issues another summoning order for "Genius Boys"!He, who had given up an annual salary of 3.6 million, also made his debut
Metaverse Post Office AI space-themed series of digital collections will be launched at 10:00 on July 30th "Yuanyou Digital Collection"
A simple change for problem, knapsack problem sets of shell
Six-faced ant financial clothing, resisting the bombardment of the interviewer, came to interview for review
ECCV 2022 | 通往数据高效的Transformer目标检测器
双碳目标下:农田温室气体排放模拟
00 testers of seasoning after nearly a year, whether to change careers or to learn the software testing students summarized the following heart advice
Eight years of testing experience, why was the leader criticized: the test documents you wrote are not as good as those of fresh graduates
Meta首份元宇宙白皮书9大看点,瞄准80万亿美元市场
Web3创始人和建设者必备指南:如何构建适合的社区?
Mac 中 MySQL 的安装与卸载
LeetCode_数位枚举_困难_233.数字 1 的个数
ToDesk版本更新,引入RTC传输技术,是否早以替代向日葵远程控制?