Spring cloud alibaba 组件gateway网关、配置管理 以及链路追踪、Jwt鉴权过滤
Spring cloud alibaba
一. Spring cloud Gateway网关
什么是网关?就是网络请求的统一入口.
为什么需要网关?
1.如果我们的有成千上万个服务,我们在请求每个服务的时候都需要进行认证,难度与工作量可想而知,要控制用户对于整个服务的访问次数的限制。
2.如果没有统一的入口,那么前端在与服务端交互的时候定位到各个服务,假设服务器端作服务的重构,那么前端也得跟着一起修改。
gateway是spring cloud的第二代网关,其性能是zuul的1.6倍左右,其内部是基于netty、reactor(多路复用)、webflux进行构建,性能强大。gateway需要从注册中心获取服务,然后通过网关来调用对应的服务。但是gateway不在web环境下运行,也就是说不能打成war包放在tomcat下运行
1.1 快速入门
1.创建springcloudalibaba-micro-service-gateway-9090子工程,导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloudalibaba-micro-service-manager</artifactId>
<groupId>com.qf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-micro-service-gateway-9090</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除掉springmvc相关的配置信息 -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
<!-- 排除掉tomcat相关的配置 -->
<exclusion>
<groupId>org.springframework.bootk</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
</dependencies>
</project>
2.创建 GateWayApplication 启动类
package com.qf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MicroServiceGateWay {
public static void main(String[] args) {
SpringApplication.run(MicroServiceGateWay.class,args);
}
}
3.创建application.yml文件
spring:
application:
name: micro-service-gateway # 网关名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
register-enabled: false # 不注册到nacos上
gateway:
discovery:
locator:
enabled: true #开启网关端口并使用服务方的名称来访问
#之前:http://localhost:8080/feign/getById?id=1002
#现在:http://localhost:9090/micro-service-consumer/feign/getById?id=1002
server:
port: 9090
4.启动服务提供方,消费方以及网关对应工程,进行测试
1.2 谓词配置
谓词(predicate)是gateway内置的的一下关于请求相关的处理,在application.yml中增加routes的配置
注意:谓词配置后,访问时就不需要再写服务名称了,因为谓词里面已经配置过了
配置谓词之前:http://localhost:9090/micro-service-consumer/feign/getUsers
spring:
application:
name: micro-service-gateway # 网关
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
register-enabled: false # 不注册到nacos上
gateway:
discovery:
locator:
enabled: true #开启之后通过网关访问:http://localhost:9090/micro-service-consumer/feign/getUsers
routes:
# id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
- id: micro-service-consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://micro-service-consumer
# 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
predicates:
# 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign/getUsers来访问了
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
# 请求的方式
- Method=get,post
# 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
- RemoteAddr=10.8.13.0/24
# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
- Header=token,[a-zA-Z0-9]+
server:
port: 9090
1.3 过滤器配置
GateWay提供了很多内置的过滤器让我们使用,具体的过滤器在spring-cloud-gateway-core-2.1.2.RELEASE.jar下的org.springframework.cloud.gateway.filter.factory包下,接下来我们挑其中一个非常常用的过滤来讲解用法,在实际的开发过程中,有这样一种业务需求,就是限制同一个IP对服务器频繁的请求,例如我们限制每个IP在每秒只能访问3次,那么要怎么实现呢?其实spring-boot已经帮我们实现好了一个,只需要做一定的配置就可以了。
IP限制的原理就是令牌桶算法,随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。如下图所示:
1.在springcloudalibaba-micro-service-gateway-9090工程中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.创建RedisHostKeyResovler类,实现获取IP的组件
package com.qf.resolver;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class RedisHostKeyResolver implements KeyResolver {
/**
* webflux:
* Mono: 用于返回单个值
* Flux: 用于返回集合数据
*/
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//获取用户的访问的 ip
String host = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(host); //构建 Mono<String>
}
}
3.在application.yml中增加filters和redis的配置
server:
port: 9090
spring:
application:
name: micro-service-gateway #服务名称,在nacos上显示
cloud:
nacos:
discovery:
enabled: true #注册服务
server-addr: 127.0.0.1:8848 #nacos访问地址
register-enabled: false #不注册到nacos
gateway:
discovery:
locator:
enabled: true #开启网关访问
#访问方式:
#原来:http://localhost:8080/feign-user-consumer/findById?id=1&origin=123
#重点掌握现在:http://localhost:9090/micro-service-consumer/feign-user-consumer/findById?id=1&origin=123
#一般不这样用:http://localhost:9090/micro-service-provider/user-provider/findById?id=1
routes:
# id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
- id: micro-service-consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://micro-service-consumer
# 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
predicates:
# 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign-user-consumer/getUsers来访问了
- Path=/feign-user-consumer/**
# 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
# 请求的方式
- Method=get,post
# 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
#- RemoteAddr=10.8.162.0/24
# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
#- Header=token,[a-zA-Z0-9]+
filters:
# RequestRateLimiter是固定值
- name: RequestRateLimiter
args:
# key-resolver是用于限流的bean对象,通过SpEL的方式 #{@XXX} 取出spring容器中的bean
keyResolver: '#{@redisHostKeyResolver}'
# 每秒往令牌桶中存放的数量
redis-rate-limiter.replenishRate: 1
# 令牌桶中最多的令牌的数量
redis-rate-limiter.burstCapacity: 3
redis:
host: 127.0.0.1
port: 6379
4.可以让局域网内的其他用户访问本机:http://本机ip:9090/feign/getUsers?origin=abc
1.4 自定义全局过滤器
1.导入依赖
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloudalibaba-micro-service-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
2.创建全局过滤器类
package com.qf.filters;
import com.alibaba.fastjson2.JSON;
import com.qf.utils.JsonResult;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
//创建多个全局过滤器
@Configuration
public class FilterConfig {
@Bean
@Order(-100)//正数值越小,负数的绝对值越大,优先级越高
public GlobalFilter loginFilter(){
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-100");
//获取传入的用户名和密码
ServerHttpRequest request = exchange.getRequest();
//获取所有参数
MultiValueMap<String, String> queryParams = request.getQueryParams();
//获取前端传过来的name以及password
List<String> nameList = queryParams.get("name");
List<String> passwordList = queryParams.get("password");
//判断
if(nameList !=null && nameList.size() > 0 && passwordList !=null && passwordList.size() > 0){
//模拟数据库查询
if("jack".equals(nameList.get(0)) && "123".equals(passwordList.get(0))){
//放行
return chain.filter(exchange);
}else{
//参数输入错误
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("name or password is error");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}else{
//参数输入错误
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("name or password is null");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}
};
}
@Bean
@Order(-90)//正数值越小,负数的绝对值越大,优先级越高
public GlobalFilter otherFilter(){
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-90");
return chain.filter(exchange);
}
};
}
}
3.浏览器访问:http://localhost:9090/feign/getUsers?origin=qwer&username=jack&password=123 进行测试
二. nacos配置管理
配置管理,就是将所有的微服务的配置统一进行管理,这样做的好处是我们的配置不用写在项目中,实现集中化的管理,方便环境的变更。
2.1 基本配置
1.在springcloud-alibaba-microservice-gateway-9090(网关)和springcloudalibaba-micro-service-consumer-8080(服务消费方)工程中都导入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.手动在桌面创建文件夹,命名为:micro-service-gateway,然后把springcloud-alibaba-microservice-gateway-9090网关工程中的application.yml中的中文注释都删了再拷贝过去,修改名称为:micro-service-gateway-dev.yml,然后再复制一份,命名改为:micro-service-gateway-test.yml,修改micro-service-gateway-test.yml中的内容,使其和micro-service-gateway-dev.yml文件的内容略有不同即可,比如:修改micro-service-gateway-test.yml 端口号为9091。
同样类似的方式,对springcloudalibaba-micro-service-consumer-8080工程中的application.yml也执行一遍。
最后,分别对micro-service-gateway和micro-service-consumer两个文件夹打成zip压缩包文件,一会要导入到nacos中使用。
3.在springcloud-alibaba-microservice-gateway-9090(网关)和springcloudalibaba-micro-service-consumer-8080(服务消费方)工程中都配置bootstrap.yml,内容如下:(group和name对应的值最好和刚才桌面上创建文件夹的名称一致),然后把两个工程中原来的application.yml命名为application.yml.bak
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
# 配置文件的后缀名
file-extension: yml
# 配置在nacos上的组名
group: micro-service-gateway
application:
# 服务名称
name: micro-service-gateway
profiles:
# 配置文件环境(生产环境,开发环境,测试环境等等,对应不同的application-*.yml文件)
active: dev
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
# 配置文件的后缀名
file-extension: yml
# 配置在nacos上的组名
group: micro-service-consumer
application:
# 服务名称
name: micro-service-consumer
profiles:
# 配置文件环境(生产环境,开发环境,测试环境等等,对应不同的application-*.yml文件)
active: dev
4.在nacos中导入新建配置文件(zip压缩包形式),参考课件提供的配置文件
5.启动工程进行测试:
配置 active: dev 则显示nacos中对应 *-dev.yml配置文件中的内容;
配置 active: test则显示nacos中对应 *-test.yml配置文件中的内容;
2.2 配置的实时刷新
1.在Controller中添加方法并测试,修改springcloudalibaba-micro-consumer-8080中FeignUserController,在FeignUserController上添加实时刷新注解@RefreshScope
@RefreshScope//实时刷新
@RestController
@RequestMapping("feign")
public class UserFeignController {
@Value("${user.username}")
private String username;
@RequestMapping("getUserName")
public String getUserName(){
return username;
}
2.在nacos面板中修改对应加载的microservice-consumer-test.yml,设置user.username的值为张三
3.启动springcloudalibaba-micro-consumer-8080工程,访问getUsername方法测试即可
4.然后把user.username的值改为李四,再次访问测试即可
三. 链路追踪
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
举个例子,在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
在本章中我们主要会讲解spring cloud sleuth整合Zipkin来实现链路的追踪,整合起来非常的简单,只需要引入相关的依赖,再加上相关的配置即可。
3.1 Sleuth基本术语
Sleuth负责记录微服务数据,Zipkin是Twitter开源的分布式跟踪系统,主要用来收集系统的时序数据,从而跟踪系统的调用问题。
span: 基本工作单元,发送一个远程调度任务 就会产生一个Span,Span是一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的,Span还有其他数据信息,比如摘要、时间戳事件、Span的ID、以及进度ID。
**Trace: ** 一系列Span组成的一个树状结构。请求一个微服务系统的API接口,这个API接口,需要调用多个微服务,调用每个微服务都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace。
**Annotation: **用来及时记录一个事件的,一些核心注解用来定义一个请求的开始和结束 。这些注解包括以下:
- cs - Client Sent -客户端发送一个请求,这个注解描述了这个Span的开始
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络传输的时间。
- ss - Server Sent (服务端发送响应)–该注解表明请求处理的完成(当请求返回客户端),如果ss的时间戳减去sr时间戳,就可以得到服务器请求的时间。
- cr - Client Received (客户端接收响应)-此时Span的结束,如果cr的时间戳减去cs时间戳便可以得到整个请求所消耗的时间。
3.2 链路追踪的搭建
Zipkin Server的下载地址:https://github.com/openzipkin/zipkin
1.启动Zipkin Server,命令如下:java -jar zipkin-server-2.19.1-exec.jar,访问:http://localhost:9411/
2.在需要追踪的服务中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
3.在nacos面板中分别修改micro-service-consumer-dev.yml和micro-service-gateway-dev.yml中配置如下内容,并分别在各自工程中的bootstrap.yml中指定调用该dev配置文件
spring:
zipkin:
base-url: http://localhost:9411/
discovery-client-enabled: false
sleuth:
sampler:
# 抽样率 100%, 默认10%, 如果服务流量较大,全部采集对存储造成的压力也会很大
probability: 0.2
D. 查看结果
四.Jwt鉴权过滤
JJWT的是在JVM上创建和验证JSON Web Token(JWTs)的库,基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
4.1 引入工具类
4.1.1在springcloudalibaba-micro-service-common中导入依赖
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
4.1.2在springcloudalibaba-micro-service-commons中创建JsonResult和JwtHelper工具类
JsonResult
package com.qf.utils;
import lombok.Data;
@Data
public class JsonResult<T> {
private String msg;
private Integer code;
private T data;
public static JsonResult ok(){
JsonResult jsonResult = new JsonResult<>();
jsonResult.setCode(200);
jsonResult.setMsg("success");
return jsonResult;
}
public static JsonResult fail(){
JsonResult jsonResult = new JsonResult<>();
jsonResult.setCode(-20000);
jsonResult.setMsg("error");
return jsonResult;
}
}
JwtHelper
package utils;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
//jwt工具类
public class JwtHelper {
// 生成Jwt
public static String jwsWithHS(SignatureAlgorithm signatureAlgorithm, String userInfo, int num, String secret) {
Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
Claims claims = Jwts.claims();
claims.setSubject(userInfo); // jwt的信息的本身
claims.setExpiration(DateUtils.addSeconds(new Date(), num)); //设置jwt多少秒过期
String jws = Jwts.builder()
.setClaims(claims).signWith(key, signatureAlgorithm).compact();
return jws;
}
// 校验Jwt
public static Jwt verifySign(String jws, String secret, SignatureAlgorithm signatureAlgorithm) {
Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(jws);
return jwt;
}
public static void main(String[] args) {
//设置,算法:HS256,用户信息:jack,过期时间:10分钟,密码:202cb962ac59075b964b07152d234b70
//System.out.println(jwsWithHS(SignatureAlgorithm.HS256, "jack", 600, "202cb962ac59075b964b07152d234b70"));
//校验jwt
String jwtstring = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYWNrIiwiZXhwIjoxNjQwOTMzMTk4fQ.igm6YSD1nBO4hT4rywa5ZPB3FXvD9gcYHEWqL8Rvk18";
Jwt jwt = verifySign(jwtstring, "202cb962ac59075b964b07152d234b70", SignatureAlgorithm.HS256);
Claims claims = (Claims)jwt.getBody();
System.out.println(claims);
//获取用户信息
System.out.println(claims.get("sub"));
}
}
4.2创建认证模块
4.2.1创建springcloudalibaba-micro-service-authentic-6060子模块,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloudalibaba-micro-service-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 均衡负载接口调用Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- 熔断降级sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- nacos配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 链路追踪 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zipkin</artifactId>-->
<!-- <version>2.2.6.RELEASE</version>-->
<!-- </dependency>-->
</dependencies>
4.2.2配置application.yml
server:
port: 6060
spring:
application:
name: micro-service-authentic #服务名称,在nacos上显示
cloud:
nacos:
discovery:
enabled: true #注册服务
server-addr: 127.0.0.1:8848 #nacos访问地址
sentinel:
transport:
port: 8719 #内部传输的端口
dashboard: localhost:8888 #sentinel控制面板访问端口
web-context-unify: false #false表示针对调用同一接口的不同url进行链路限制
#feign配合sentinel使用
feign:
sentinel:
enabled: true
#配置签名和算法
jwt:
secret: 21232f297a57a5a743894a0e4a801fc3
signature-algorithm: HS256
4.2.3编写启动类
package com.qf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient//注册服务
@EnableFeignClients//feign
public class MicroServiceAuthenticApplication {
public static void main(String[] args) {
SpringApplication.run(MicroServiceAuthenticApplication.class,args);
}
}
4.2.4编写Controller
package com.qf.controller;
import com.qf.utils.JsonResult;
import com.qf.utils.JwtHelper;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/authentic")
public class AuthenticController {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.signature-algorithm}")
private String algorithm;
@RequestMapping("login")
public JsonResult login(String usercode,String password){
//判断传入的参数
if((usercode != null && usercode.length() > 0) && (password != null && password.length() > 0) ){
//模拟数据库进行比对
if("jack".equals(usercode) && "123".equals(password)){
//登录成功,生成jwt
String jwt = JwtHelper.jwsWithHS(SignatureAlgorithm.forName(algorithm), usercode, 600, secret);
//返回
JsonResult jsonResult = JsonResult.ok();
jsonResult.setData(jwt);
return jsonResult;
}else {
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("login is fail");
return jsonResult;
}
}else{
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("usercode or password is null");
return jsonResult;
}
}
}
4.3 配置网关
4.3.1在springcloudalibaba-micro-service-gateway-9090导入公共模块依赖
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloudalibaba-micro-service-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4.3.2在springcloudalibaba-micro-service-gateway-9090的application.yml中配置谓词和jwt
server:
port: 9090
spring:
application:
name: micro-service-gateway #服务名称,在nacos上显示
cloud:
nacos:
discovery:
enabled: true #注册服务
server-addr: 127.0.0.1:8848 #nacos访问地址
register-enabled: false #不注册到nacos
gateway:
discovery:
locator:
enabled: true #开启网关访问
#访问方式:
#原来: http://localhost:8080/feign/findById?id=1&origin=123
#重点掌握现在:http://localhost:9090/micro-service-consumer/feign/findById?id=1&origin=123
routes:
# 配置认证服务
- id: micro-service-authentic
uri: lb://micro-service-authentic
predicates:
- Path=/authentic/**
# id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
- id: micro-service-consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://micro-service-consumer
# 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
predicates:
# 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign/getUsers来访问了
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
# 请求的方式
- Method=get,post
# 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
#- RemoteAddr=10.8.162.0/24
# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
#- Header=token,[a-zA-Z0-9]+
# filters:
# # RequestRateLimiter是固定值
# - name: RequestRateLimiter
# args:
# # key-resolver是用于限流的bean对象,通过SpEL的方式 #{@XXX} 取出spring容器中的bean
# keyResolver: '#{@redisHostKeyResolver}'
# # 每秒往令牌桶中存放的数量
# redis-rate-limiter.replenishRate: 1
# # 令牌桶中最多的令牌的数量
# redis-rate-limiter.burstCapacity: 3
#
# redis:
# host: 127.0.0.1
# port: 6379
#配置签名和算法
jwt:
secret: 21232f297a57a5a743894a0e4a801fc3
signature-algorithm: HS256
4.3.3在springcloudalibaba-micro-service-gateway-9090创建AuthFilter过滤器
package com.qf.filters;
import com.alibaba.fastjson2.JSON;
import com.qf.utils.JsonResult;
import com.qf.utils.JwtHelper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.signature-algorithm}")
private String algorithm;
//校验token
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//请求路径
String requestPath = exchange.getRequest().getPath().toString();
System.out.println(requestPath);
//判断,如果是登录,直接放行
if(requestPath.contains("/authentic/login")){
return chain.filter(exchange);
}
//访问的是其他服务
//获取token进行校验
String token = exchange.getRequest().getHeaders().getFirst("token");
//判断
if(token == null){
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("token is null");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}else{
try {
//校验
Jwt jwt = JwtHelper.verifySign(token, secret, SignatureAlgorithm.forName(algorithm));
Claims claims = (Claims)jwt.getBody();
String sub = claims.get("sub").toString();
System.out.println("sub:" + sub);
if("jack".equals(sub)){
return chain.filter(exchange);
}else{
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData("usercode is error");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}catch (Exception e){
JsonResult jsonResult = JsonResult.fail();
jsonResult.setData(e.getMessage());
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}
}
@Override
public int getOrder() {
//正数值越小,负数的绝对值越大,优先级越高
return 1;
}
}