✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏
📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏
📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏
💖💖如果文章对你有所帮助请留下三连✨✨
衔接上篇: [Spring Cloud] GateWay服务网关_沫洺的博客-CSDN博客
官方定义全局过滤器示例
接下来仿照示例创建我们自己的全局过滤器
需求: 进行token验证(网关可以理解为一个交换机,有request和response),将原来的request通过验证并解析,过滤后构建新的request来正常调用后方的具体接口
子模块:spring-cloud-gateway
pom添加个hutool依赖
cn.hutool hutool-all application.properties
spring.cloud.gateway.routes[0].id = route1 spring.cloud.gateway.routes[0].predicates[0] = Path=/test/** spring.cloud.gateway.routes[0].filters[0] = StripPrefix=1 spring.cloud.gateway.routes[0].uri = http://httpbin.org解析对象UserInfo
@Data public class UserInfo {private Integer userId;private String nickName; }全局过滤器 CustomGlobalFilter
@Component public class CustomGlobalFilter implements GlobalFilter, Ordered {//密钥private static final String TOKEN_SECRET="moming123";@Beanpublic GlobalFilter customFilter(){return new CustomGlobalFilter();}@Overridepublic Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//获取请求头Authorization存放的JWT结构的token数据List tokenList = request.getHeaders().get("Authorization");//如果为空,报401异常if(ObjectUtil.isEmpty(tokenList)){response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//不为空则获取tokenString token = tokenList.get(0);boolean isTrue = false;try {//验证密钥isTrue = JWTUtil.verify(token, TOKEN_SECRET.getBytes());} catch (Exception e) {e.printStackTrace();}//不匹配报401if(isTrue==false){response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//解析tokenfinal JWT jwt = JWTUtil.parseToken(token);//获取JWT负载部分,转成UserInfo对象//System.out.println(jwt.getPayload().toString());UserInfo userInfo = JSONUtil.toBean(jwt.getPayloads().toString(), UserInfo.class);Integer userId = userInfo.getUserId();//将userId通过header传到后端//原来的request是只读的,需要通过mutate复制一份新的request2 并添加请求头ServerHttpRequest request2 = request.mutate().header("userId", String.valueOf(userId)).build();//可以正常调用后方的具体接口//交换机也是不能改动所以复制一份,并发送新的request2return chain.filter(exchange.mutate().request(request2).build());}@Overridepublic int getOrder() {//越小优先级越高return -1;} } 通过启动类生成一个JWT结构的token
@SpringBootApplication public class GatewayApp {public static void main(String[] args) {SpringApplication.run(GatewayApp.class, args);Mapmap = new HashMap<>();map.put("userId", 1);map.put("nickName", "moming");map.put("expire_time", System.currentTimeMillis()+1000*60*60*24*5);String token = JWTUtil.createToken(map, "moming123".getBytes());System.out.println(token);} } 运行
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6Im1vbWluZyIsImV4cGlyZV90aW1lIjoxNjY5NjMxMDcxNDE4LCJ1c2VySWQiOjF9.wJqasnKRQwdB-u-pm6hzqcTlfRQwX2ioHHmROspBt7Q
token错误场景
JWT回顾:[SpringBoot-vue3]用户登录实现JWT单点登录/ThreadLocal保存用户信息_沫洺的博客-CSDN博客参考博文: JSON Web Token 入门教程
局部过滤器是只针对某个网关(路由)去生效
全局过滤器实现的是GlobalFilter,而局部过滤器实现的是GatewayFilter
定义一个局部过滤器
需求: 呈现网关调用接口耗时
这里网关调用接口服务时是异步的,比如说网关通过某个线程1去访问a服务,访问结束后返回数据时走的又是另外一个线程2,所以是异步的
那么我们要统计耗时开始时间就不能通过线程1来统计(原因是线程1可能去调用其他服务,会设置新的开始时间,),但交换机只有一个,所以可以给交换机设置一个属性开始时间戳,交换机开始作用到调用结束就是网关调用接口的耗时
@Slf4j public class LoggerGatewayFilter implements GatewayFilter, Ordered {@Overridepublic Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) {//开始调用//设置开始时间戳exchange.getAttributes().put("start",System.currentTimeMillis());ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//过滤之后then异步执行一个方法return chain.filter(exchange).then(Mono.fromRunnable(()->{//运行结束long end = System.currentTimeMillis();long start = exchange.getAttribute("start");long msTime = end - start;//获取接口String rawPath = request.getURI().getRawPath();log.info("接口:{} -> 耗时:{}",rawPath,msTime);}));}@Overridepublic int getOrder() {return -1;} } 但是局部过滤器不能像全局过滤器那样定义完放到springioc中就可以使用,要通过工厂来将我们自定义的局部过滤器生产出来,而且既然是局部的,那必然要配置指定的网关去生效,来达到按需加载的效果
配置时有个命名规则
参考内置的局部过滤器的类的命名方式及配置的命名方式
AddRequestHeaderGatewayFilterFactory======>AddRequestHeader
AddRequestParameterGatewayFilterFactory======>AddRequestParameter
所以我们自定义时的类名要加GatewayFilterFactory,配置时用前面部分即可
比如我们自定义的LoggerGatewayFilterFactory ======>Logger
@Component public class LoggerGatewayFilterFactory extends AbstractGatewayFilterFactory配置
spring.cloud.gateway.routes[0].id = route1 spring.cloud.gateway.routes[0].predicates[0] = Path=/test/** spring.cloud.gateway.routes[0].filters[0] = StripPrefix=1 spring.cloud.gateway.routes[0].filters[1] = Logger spring.cloud.gateway.routes[0].uri = http://httpbin.org注释掉自定义全局过滤器相关内容,运行访问
注释掉局部过滤器相关内容
调用a服务
添加依赖
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 配置nacos
#与nacos服务注册中心结合 spring.cloud.gateway.routes[1].id = route_nacos spring.cloud.gateway.routes[1].predicates[0] = Path=/nacos-a/** spring.cloud.gateway.routes[1].filters[0] = StripPrefix=1 #前置过滤器 spring.cloud.gateway.routes[1].filters[1] = AddRequestHeader=name,moming spring.cloud.gateway.routes[1].uri = lb://nacos-a启动nacos服务器
修改子模块spring-cloud-nacos-a
@RestController public class UserServiceController implements IUserService {@Value("${server.port}")private String port;@Overridepublic String getName(Integer id) {HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();String name = request.getHeader("name");return StrUtil.format("nacos a({}) header:{} 返回的id: {}" ,this.port,name,id);}@Overridepublic Integer getAmount(Integer id) {return id*100;}@Overridepublic String getSleep(Integer time) {ThreadUtil.sleep(time*1000);return StrUtil.format("nacos a({}) 睡眠了 {} 秒",this.port ,time);} }打包运行a服务
准备工作做完,直接运行访问
在启动一个a服务
访问