SpringCloudGateway网关实战(三)
SpringCloudGateway网关实战(三)
上一章节我们讲了gateway的内置过滤器Filter,本章节我们来讲讲全局过滤器。
自带全局过滤器
在实现自定义全局过滤器前, spring-cloud-starter-gateway
依赖本身就自带一些全局过滤器,我们举些比较常用的例子:
- NettyRoutingFilter:该过滤器使用Netty作为底层的HTTP客户端,负责将请求转发到下游服务。
- RouteToRequestUrlFilter:该过滤器将根据路由配置中的URI信息,将请求转发到指定的URL。
- WebsocketRoutingFilter:该过滤器用于处理WebSocket协议的请求转发。
- GatewayMetricsFilter:该过滤器用于收集网关的基本性能指标数据,例如请求的数量、响应时间等。
自定义全局过滤器
自定义全局过滤器需要实现GlobalFilter
接口和Ordered
接口。
AuthFilter
token验证全局过滤器
引入依赖:
<!-- Jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
具体代码:
@Component
public class AuthFilter implements GlobalFilter, Ordered {private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);@Autowiredprivate IgnoreWhiteProperties ignoreWhite;@Autowiredpublic RedisTemplate redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpRequest.Builder mutate = request.mutate();// 跳过白名单String url = request.getURI().getPath();if (com.smallred.gateway.utils.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 = redisTemplate.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, "user_key", userKey);addHeader(mutate, "user_id", userId);addHeader(mutate, "username", userName);// 内部请求来源参数清除removeHeader(mutate, "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 = urlEncode(valueStr);mutate.header(name, valueEncode);}/*** 删除头*/private void removeHeader(ServerHttpRequest.Builder mutate, String name) {mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();}/*** 获取缓存key*/private String getTokenKey(String token) {return "login_tokens:" + token;}/*** 获取请求token*/private String getToken(ServerHttpRequest request) {String token = request.getHeaders().getFirst("Authorization");// 如果前端设置了令牌前缀,则裁剪掉前缀if (StringUtils.isNotEmpty(token) && token.startsWith("Bearer")) {token = token.replaceFirst("Bearer", StringUtils.EMPTY);}return token;}/*** 内容编码** @param str 内容* @return 编码后的内容*/public static String urlEncode(String str){try{return URLEncoder.encode(str, "UTF-8");}catch (UnsupportedEncodingException e){return StringUtils.EMPTY;}}/*** 验证失败返回*/private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, 401);}@Overridepublic int getOrder() {return -200;}
}
依赖类
白名单:
@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;}
}
白名单配置:
# 安全配置
security:ignore:whites:- /auth/logout- /auth/login- /auth/register- /*/v2/api-docs- /csrf
StringUtils类:
public class StringUtils {/** 空字符串 */private static final String NULLSTR = "";public static boolean matches(String str, List<String> strs){if (isEmpty(str) || isEmpty(strs)){return false;}for (String pattern : strs){if (isMatch(pattern, str)){return true;}}return false;}public static boolean isEmpty(String str){return isNull(str) || NULLSTR.equals(str.trim());}public static boolean isEmpty(Collection<?> coll){return isNull(coll) || coll.isEmpty();}public static boolean isNull(Object object){return object == null;}public static boolean isMatch(String pattern, String url){AntPathMatcher matcher = new AntPathMatcher();return matcher.match(pattern, url);}}
ServletUtils类:
public class ServletUtils {public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code){return webFluxResponseWriter(response, HttpStatus.OK, value, code);}public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code){return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);}public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code){response.setStatusCode(status);response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);R<?> result = R.fail(code, value.toString());DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());return response.writeWith(Mono.just(dataBuffer));}}
JwtUtils类:
public class JwtUtils {public static String secret = "abcdefghijklmnopqrstuvwxyz";/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/public static String createToken(Map<String, Object> claims){String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/public static Claims parseToken(String token){return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}/*** 根据令牌获取用户标识** @param token 令牌* @return 用户ID*/public static String getUserKey(String token){Claims claims = parseToken(token);return getValue(claims, "user_key");}/*** 根据令牌获取用户标识** @param claims 身份信息* @return 用户ID*/public static String getUserKey(Claims claims){return getValue(claims, "user_key");}/*** 根据令牌获取用户ID** @param token 令牌* @return 用户ID*/public static String getUserId(String token){Claims claims = parseToken(token);return getValue(claims, "user_id");}/*** 根据身份信息获取用户ID** @param claims 身份信息* @return 用户ID*/public static String getUserId(Claims claims){return getValue(claims, "user_id");}/*** 根据令牌获取用户名** @param token 令牌* @return 用户名*/public static String getUserName(String token){Claims claims = parseToken(token);return getValue(claims, "username");}/*** 根据身份信息获取用户名** @param claims 身份信息* @return 用户名*/public static String getUserName(Claims claims){return getValue(claims, "username");}/*** 根据身份信息获取键值** @param claims 身份信息* @param key 键* @return 值*/public static String getValue(Claims claims, String key){return toStr(claims.get(key), "");}public static String toStr(Object value, String defaultValue){if (null == value){return defaultValue;}if (value instanceof String){return (String) value;}return value.toString();}}
IpAddressFilter
根据请求记录ip地址日志:
@Component
public class IpAddressFilter implements GlobalFilter, Ordered {public static final Map<String, AtomicInteger> CACHE = new ConcurrentHashMap<>();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {InetSocketAddress host = exchange.getRequest().getHeaders().getHost();if (host == null || host.getHostName() == null) {exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}String hostName = host.getHostName();AtomicInteger count = CACHE.getOrDefault(hostName, new AtomicInteger(0));count.incrementAndGet();CACHE.put(hostName, count);System.out.println("IP地址:" + hostName + ",访问次数:" + count.intValue());return chain.filter(exchange);}@Overridepublic int getOrder() {return 101;}
}
相关文章:
SpringCloudGateway网关实战(三)
SpringCloudGateway网关实战(三) 上一章节我们讲了gateway的内置过滤器Filter,本章节我们来讲讲全局过滤器。 自带全局过滤器 在实现自定义全局过滤器前, spring-cloud-starter-gateway依赖本身就自带一些全局过滤器࿰…...
08在MyBatis-Plus中配置多数据源
配置多数据源 模拟多库场景 适用于多种场景: 多库(操作的表分布在不同数据库当中),读写分离(有的数据库负责查询的功能,有的数据库负责增删该的功能),一主多从,混合模式等 第一步: 模拟多库,在mybatis_plus数据库中创建user表,在mybatis_plus_1数据库中创建product表 --创建…...

Centos8安装docker并配置Kali Linux图形化界面
鉴于目前网上没有完整的好用的docker安装kali桌面连接的教程,所以我想做一个。 准备工作 麻了,这服务器供应商提供的镜像是真的纯净,纯净到啥都没有。 问题一:Centos8源有问题 Error: Failed to download metadata for repo ap…...

游戏开发初等数学基础
凑数图() 立体图形面积体积 1. 立方体(Cube): 表面积公式: 6 a 2 6a^2 6a2 (其中 a a a 是边长)。体积公式: a 3 a^3 a3 (其中 a a a 是边长)。 2. 球体(Sphere): 表面积公…...
svg图片代码data:image/svg+xml转png图片方法
把代码保存为html格式的文件中,用浏览器访问,即可右键保存 从AI软件或其它网站得到svg图片代码后,把他复制到下面源码上 注意:src""图片地址中,一些参数的含义 d‘这里是图片代码数据’ viewBox是图片显示区域,宽,高等 fill%23000000’这里表示颜色 ,后面6位0表示黑色…...
解决问题:Replace `‘vue‘;⏎` with `“vue“;`
使用vscode写vue文件的问题: Replace vue;⏎ with "vue"; error Replace v-model:value"xxx"placeholder"inputsearch prettier/prettier 7:38 error Insert ⏎ potentially fixable with the --fix option 原因:格式问题&a…...

ThinkPHP 5.0通过composer升级到5.1,超级简单
事情是这样的,我实现一个验证码登录的功能,但是这个验证码的包提示tp5的版本可以是5.1.1、5.1.2、5.1.3。但我使用的是5.0,既然这样,那就升个级呗,百度了一下,结果发现大部分都是讲先备份application和修改…...

计算机竞赛 多目标跟踪算法 实时检测 - opencv 深度学习 机器视觉
文章目录 0 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 深度学习多目标跟踪 …...

一文了解大模型工作原理——以ChatGPT为例
文章目录 写在前面1.Tansformer架构模型2.ChatGPT原理3.提示学习与大模型能力的涌现3.1 提示学习3.2 上下文学习3.3 思维链 4.行业参考建议4.1 拥抱变化4.2 定位清晰4.3 合规可控4.4 经验沉淀 写在前面 2022年11月30日,ChatGPT模型问世后,立刻在全球范围…...
CPP-Templates-2nd--第十九章 萃取的实现 19.7---
目录 19.7 其它的萃取技术 19.7.1 If-Then-Else 19.7.2 探测不抛出异常的操作 19.7.3 萃取的便捷性(Traits Convenience) 别名模板和萃取(Alias Templates And Traits) 变量模板和萃取(Variable Templates and Traits&…...

python 采用selenium+cookies 获取登录后的网页
百度网页由于需要登陆手机短信验证。比较麻烦 这里我采用先人工登录百度账号,然后将百度账号的相关cookies保存下来 然后采用selenium动态登录网页 整体代码如下 from selenium import webdriverimport timeoptions webdriver.ChromeOptions()options.add_argu…...

【测试开发】答疑篇 · 什么是软件测试
【测试开发】答疑篇 文章目录 【测试开发】答疑篇1. 生活中的测试2. 什么是软件测试3. 为什么要有测试/没有测试行不行4. 软件测试和软件开发的区别5. 软件测试和软件调试之间的区别6. 软件测试的岗位7. 优秀测试人员具备的素质 【测试开发】答疑篇 软件不一定是桌面应用&#…...

深入解析顺序表:揭开数据结构的奥秘,掌握顺序表的精髓
💓 博客主页:江池俊的博客⏩ 收录专栏:数据结构探索👉专栏推荐:✅C语言初阶之路 ✅C语言进阶之路💻代码仓库:江池俊的代码仓库🔥编译环境:Visual Studio 2022Ἰ…...

数据风险量化评估方案
一、企业面临数据安全的痛点 1、企业缺少清晰的数据安全意识 各部门重视度不够,缺少主动数据安全管控意识。数据安全管控架构不清晰,职责划分不明确。对数据安全管控认识不全面、不深刻。工作人员对于所持有的数据缺乏概念,导致数据的价值无…...

EasyAVFilter代码示例之将视频点播文件转码成HLS(m3u8+ts)视频点播格式
以下是一套完整的视频点播功能开发源码,就简简单单几行代码,就可以完成原来ffmpeg很复杂的视频点播转码调用流程,而且还可以集成在自己的应用程序中调用,例如java、php、cgo、c、nodejs,不需要再单独一个ffmpeg的进程来…...
day-50 代码随想录算法训练营(19)动态规划 part 11
123.买卖股票的最佳时机||| 分析:只能买卖两次,就是说有五个状态: 没有买过第一次买入第一次卖出第二次买入第二次卖出 思路:二维数组,记录五个状态 1.dp存储:dp[i][1] 第一次买入 dp[i][2] 第一次卖…...

自定义权限指令与防止连点指令
1.权限指令 // 注册一个全局自定义权限指令 v-permission Vue.directive(permission, {inserted: function(el, binding, vnode) {const {value} binding; // 指令传的值// user:edit:phone,sysData:sampleconst permissions [user:edit:address, sysData:entrust, sysData:…...

UE5、CesiumForUnreal实现瓦片坐标信息图层效果
文章目录 1.实现目标2.实现过程2.1 原理简介2.2 cesium-native改造2.3 CesiumForUnreal改造2.4 运行测试3.参考资料1.实现目标 参考CesiumJs的TileCoordinatesImageryProvider,在CesiumForUnreal中也实现瓦片坐标信息图层的效果,便于后面在调试地形和影像瓦片的加载调度等过…...
PostgreSQL执行计划
1. EXPLAIN命令 1)PostgreSQL中EXPLAIN命令的语法格式: postgres# \h explain Command: EXPLAIN Description: show the execution plan of a statement Syntax: EXPLAIN [ ( option [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statementwhere option can be…...
【2023 睿思芯科 笔试题】~ 题目及参考答案
文章目录 1. 题目 & 答案单选题编程题问题1:解析1:问题2:解析2: 声明 名称如标题所示,希望大家正确食用(点赞转发评论) 本次笔试题以两种形式考察的,分别是:选择题&a…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
跨平台商品数据接口的标准化与规范化发展路径:淘宝京东拼多多的最新实践
在电商行业蓬勃发展的当下,多平台运营已成为众多商家的必然选择。然而,不同电商平台在商品数据接口方面存在差异,导致商家在跨平台运营时面临诸多挑战,如数据对接困难、运营效率低下、用户体验不一致等。跨平台商品数据接口的标准…...

MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...

DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...