✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 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依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency>
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"; @Bean public GlobalFilter customFilter(){ return new CustomGlobalFilter(); } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); //获取请求头Authorization存放的JWT结构的token数据 List<String> tokenList = request.getHeaders().get("Authorization"); //如果为空,报401异常 if(ObjectUtil.isEmpty(tokenList)){ response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } //不为空则获取token String token = tokenList.get(0); boolean isTrue = false; try { //验证密钥 isTrue = JWTUtil.verify(token, TOKEN_SECRET.getBytes()); } catch (Exception e) { e.printStackTrace(); } //不匹配报401 if(isTrue==false){ response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } //解析token final 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(); //可以正常调用后方的具体接口 //交换机也是不能改动所以复制一份,并发送新的request2 return chain.filter(exchange.mutate().request(request2).build()); } @Override public int getOrder() { //越小优先级越高 return -1; } }
通过启动类生成一个JWT结构的token
@SpringBootApplication public class GatewayApp { public static void main(String[] args) { SpringApplication.run(GatewayApp.class, args); Map<String,Object> map = 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 { @Override public Mono<Void> filter(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); })); } @Override public int getOrder() { return -1; } }
但是局部过滤器不能像全局过滤器那样定义完放到springioc中就可以使用,要通过工厂来将我们自定义的局部过滤器生产出来,而且既然是局部的,那必然要配置指定的网关去生效,来达到按需加载的效果
配置时有个命名规则
参考内置的局部过滤器的类的命名方式及配置的命名方式
AddRequestHeaderGatewayFilterFactory======>AddRequestHeader
AddRequestParameterGatewayFilterFactory======>AddRequestParameter
所以我们自定义时的类名要加GatewayFilterFactory,配置时用前面部分即可
比如我们自定义的LoggerGatewayFilterFactory ======>Logger
@Component public class LoggerGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new LoggerGatewayFilter(); } }
配置
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
注释掉自定义全局过滤器相关内容,运行访问
🌾结合Nacos服务注册中心
注释掉局部过滤器相关内容
调用a服务
添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <!--<version>2.2.1.RELEASE</version>--> </dependency>
配置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; @Override public 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); } @Override public Integer getAmount(Integer id) { return id*100; } @Override public String getSleep(Integer time) { ThreadUtil.sleep(time*1000); return StrUtil.format("nacos a({}) 睡眠了 {} 秒",this.port ,time); } }
打包运行a服务
准备工作做完,直接运行访问
在启动一个a服务
访问