Spring cloud alibaba
一. Spring cloud Gateway网关
什么是网关?就是网络请求的统一入口. 为什么需要网关? 1.如果我们的有成千上万个服务,我们在请求每个服务的时候都需要进行认证,难度与工作量可想而知,要控制用户对于整个服务的访问次数的限制。 2.如果没有统一的入口,那么前端在与服务端交互的时候定位到各个服务,假设服务器端作服务的重构,那么前端也得跟着一起修改。 gateway是spring cloud的第二代网关,未来会取代zuul,其性能是zuul的1.6倍左右,其内部是基于netty、reactor(多路复用)、webflux进行构建,性能强大。gateway需要从注册中心获取服务,然后通过网关来调用对应的服务。但是gateway不在web环境下运行,也就是说不能打成war包放在tomcat下运行
1.1 使用
1.创建springcloud-alibaba-microservice-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>springcloud-alibaba-microservice-manager</artifactId>
<groupId>com.qf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-alibaba-microservice-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>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- 网关相关配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.创建 GateWayApplication 启动类
package com.qf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class,args);
}
}
3.创建
spring:
application:
# 网关名称
name: microservice-gateway
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
#表示是否将服务注册到nacos上,false表示不注册,默认true
register-enabled: false
gateway:
discovery:
locator:
# 开启之后可以使用网关地址进行访问
# 之前:http://localhost:8080/feign/getAll
# 现在:http://localhost:9090/microservice-consumer/feign/getAll
enabled: true
server:
port: 9090
1.2 谓词配置
谓词(predicate)是gateway内置的的一下关于请求相关的处理,在application.yml中增加配置
spring:
application:
# 网关名称
name: microservice-gateway
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
#表示是否将服务注册到nacos上,false表示不注册,默认true
register-enabled: false
gateway:
discovery:
locator:
# 开启之后可以使用网关地址进行访问
# 之前:http://localhost:8080/feign/getAll
# 现在:http://localhost:9090/microservice-consumer/feign/getAll
enabled: true
routes:
# id可以不为服务名,名字随意,但是不能重复。推荐使用服务名的方式,不暴露服务名
- id: microservice-consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://microservice-consumer
# 谓词: 就是满足的条件. 可以在 org.springframework.cloud.gateway.handler.predicate 这个包下
predicates:
# 配置访问controller的一级目录,即可通过http://localhost:9090/feign/getAll来访问
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合 [a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
#请求的方式
- Method=get,post
# 设置时间区间内访问
- After=2019-11-12T00:00:00+08:00[Asia/Shanghai]
- Before=2020-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述从172.18.96.1~172.18.96.255的ip地址才可以访问
# - RemoteAddr=172.18.96.0/24
# 请求的头中必须得携带 token, value值符合 [a-zA-Z0-9]+
# - Header=token,[a-zA-Z0-9]+
server:
port: 9090
id: 可以配置很多的路由信息,但是每个路由都有一个唯一的id来标识。
uri: 转发的地址,lb://开头标识转发到微服务的内部的某个服务。
predicates: 配置谓词。
Path: 配置请求的路径
Query: 请求的参数
Method: 请求的方式,大小写不敏感
After: 在指定的时间之后
Before: 在指定的时间之前
RemoteAddr: 请求的IP的来源
Header: 请求头
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.在springcloud-alibaba-microservice-gateway-9090工程中导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2.创建HostAddrKeyResolver类,实现获取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 RedisHostKeyResovler 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中增加配置
spring:
application:
# 网关名称
name: microservice-gateway
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
#表示是否将服务注册到nacos上,false表示不注册,默认true
register-enabled: false
gateway:
discovery:
locator:
# 开启之后可以使用网关地址进行访问
# 之前:http://localhost:8080/feign/getAll
# 现在:http://localhost:9090/microservice-consumer/feign/getAll
enabled: true
routes:
# id可以不为服务名,名字随意,但是不能重复。推荐使用服务名的方式
- id: microservice-consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://microservice-consumer
# 谓词: 就是满足的条件. 可以在 org.springframework.cloud.gateway.handler.predicate 这个包下
predicates:
# 配置controller的一级目录,即可通过http://localhost:9090/feign/getAll来访问
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合 [a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
#请求的方式
- Method=get,post
# 设置时间区间内访问
- After=2019-11-12T00:00:00+08:00[Asia/Shanghai]
- Before=2020-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述从172.18.96.1~172.18.96.255的ip地址才可以访问
# - RemoteAddr=172.18.96.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: '#{@redisHostKeyResovler}'
# 每秒往令牌桶中存放的数量
redis-rate-limiter.replenishRate: 1
# 令牌桶中最多的令牌的数量
redis-rate-limiter.burstCapacity: 3
redis:
port: 6379
host: localhost
server:
port: 9090
4.可以让局域网内的其他用户访问本机:http://本机ip:9090/feign/getUsers?origin=abc
1.4 自定义全局过滤器
1.创建全局过滤器类
package com.qf.filter;
import com.alibaba.fastjson.JSONObject;
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.HttpStatus;
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.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class FilterConfig {
@Bean
@Order(-100)//过滤器加载顺序,负数值越大,正数值越小,则先执行,尽量使用负值,避免与系统自带的过滤器冲突
public GlobalFilter loginFilter() {
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// TODO 登录放过
System.out.println("登录的全局过滤器");
ServerHttpRequest request = exchange.getRequest();
// key是字符串, value值是list的集合
MultiValueMap<String, String> multiValueMap = request.getQueryParams();
List<String> usernameList = multiValueMap.get("username");
List<String> passwordList = multiValueMap.get("password");
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> result = new HashMap<>();
// 设置响应的状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置响应的头信息
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
// 请求的参数中没有用户名和密码
if(null == usernameList || usernameList.size() == 0 || null == passwordList || passwordList.size() == 0) {
result.put("code", -1);
result.put("msg", "username and password is required.");
// 得到json数据
byte[] bs = JSONObject.toJSONString(result).getBytes(Charset.defaultCharset());
// 获取 DataBuffer
DataBuffer dataBuffer = response.bufferFactory().wrap(bs);
return response.writeWith(Mono.just(dataBuffer));
}else {
String username = usernameList.get(0);
String password = passwordList.get(0);
if("jack".equals(username) && "123".equals(password)) {
return chain.filter(exchange); // 接着往后走
}else {
result.put("code", -1);
result.put("msg", "login fail");
byte[] bs = JSONObject.toJSONString(result).getBytes(Charset.defaultCharset());
// 获取 DataBuffer
DataBuffer dataBuffer = response.bufferFactory().wrap(bs);
return response.writeWith(Mono.just(dataBuffer));
}
}
}
};
}
@Bean
@Order(-99)
public GlobalFilter otherFilter() {
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("其他的全局过滤器");
return chain.filter(exchange);
}
};
}
}
2.浏览器访问:http://localhost:9090/feign/getUsers?origin=qwer&username=jack&password=123 进行测试
二. nacos配置管理路径
配置管理,就是将所有的微服务的配置统一进行管理,这样做的好处是我们的配置不用写在项目中,实现集中化的管理,方便环境的变更。
2.1 基本配置
1.在springcloud-alibaba-microservice-gateway-9090和springcloud-alibaba-microservice-consumer-8080工程中都导入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
将原来的application.yml拷贝到手动创建的文件夹并打包,然后设置失效,即重新命名为application.yml.bak,创建bootstrap.yml配置文件来连接nacos,需要在springcloud-alibaba-microservice-gateway-9090和springcloud-alibaba-microservice-consumer-8080工程中都配置bootstrap.yml,分别为
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
# 配置文件的后缀名
file-extension: yml
# 配置在nacos上的组名
group: microservice-gateway
application:
# 服务名称
name: microservice-gateway
profiles:
# 配置文件环境(生产环境,开发环境,测试环境等等,对应不同的application-*.yml文件)
active: dev
在nacos中新建配置文件的时候,文件名的命名规则是 ${spring.application.name}-${spring.profile.active}.yml,对应到上面的配置就是 microservice-gateway-dev.yml(参考课件提供的配置文件)
2.2 配置的实时刷新
1.在Controller中添加方法并测试,修改springcloud-alibaba-microservice-consumer-8080中FeignUserController
@Value("${user.username}")
private String username;
@RequestMapping("getUserName")
public String getUsername(){
return username;
}
2.在nacos面板中分别修改microservice-consumer-dev.yml和microservice-consumer-test.yml
3.修改springcloud-alibaba-microservice-consumer-8080中bootstrap.yml的配置
4.重启springcloud-alibaba-microservice-consumer-8080进行测试,如果访问不到,可在FeignUserController上添加@RefreshScope再次测试
三. 链路追踪
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
举个例子,在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E,后端经过一系列的业务逻辑计算最后将数据返回给用户。对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
在本章中我们主要会讲解spring cloud sleuth整合Zipkin来实现链路的追踪,整合起来非常的简单,只需要引入相关的依赖,再加上相关的配置即可。
3.1 Sleuth基本术语
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时间戳便可以得到整个请求所消耗的时间。
Zipkin是Twitter开源的分布式跟踪系统,主要用来收集系统的时序数据,从而跟踪系统的调用问题。
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.在springcloud-alibaba-microservice-gateway-9090中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
3.在nacos面板中分别修改microservice-consumer-dev.yml和microservice-gateway-dev.yml中配置如下内容,并分别在各自工程中的bootstrap.yml中指定调用该文件
spring:
zipkin:
base-url: http://localhost:9411/
discovery-client-enabled: false
sleuth:
sampler:
# 抽样率 100%, 默认10%
probability: 0.2
四.Jwt
4.1 介绍
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的,常用于单点登录。
4.2 使用场景
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
4.3 结构
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:头部,载荷,签名
Header
- Payload
-
4.4 身份认证
HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证.
传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。
这种基于服务器的身份认证方式存在一些问题: Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
- Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
- CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
- CSRF : 用户很容易受到CSRF攻击。
使用Jwt的好处:
- 无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
- 安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。即使在你的实现中将token存储到客户端的Cookie中,这个Cookie也只是一种存储机制,而非身份认证机制。没有基于会话的信息可以操作,因为我们没有会话!
4.5 JWT入门
导入依赖
测试 ```java package com.qf.jwt;<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>
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.Test;
import java.util.Calendar; import java.util.HashMap;
public class TestJwt {
@Test
public void createJwt(){
//头部信息(一般不传,使用默认值)
HashMap<String, Object> map = new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");
//时间对象
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,30);
//创建Jwt
String token = JWT.create()
.withExpiresAt(calendar.getTime())//过期时间
.withHeader(map)//头部信息
.withClaim("username", "张三")//载荷
.withClaim("num", 12345)
.sign(Algorithm.HMAC512("qwer"));//签名
//输出
System.out.println(token);
}
@Test
public void verifyJwt(){
//算法和签名要和之前一致
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512("qwer")).build();
//校验
DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJudW0iOjEyMzQ1LCJleHAiOjE2MTg4NTE0MDksInVzZXJuYW1lIjoi5byg5LiJIn0.MNsbA-X0MZDSqdxTx2fw8R6DBE26TtieSNXRDd7Jj757Yi1GoPeXLULjhxREzER5AOpcuK8zHYGF0biLgjTvIQ");
//输出
System.out.println(decodedJWT.getHeader());
System.out.println(decodedJWT.getPayload());
System.out.println(decodedJWT.getSignature());
System.out.println(decodedJWT.getToken());
System.out.println(decodedJWT.getClaim("username").asString());
System.out.println(decodedJWT.getClaim("num").asInt());
}
}
Jwt工具类
```java
package com.qf.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JwtUtils {
private static final String signature = "qwer";
public static String createJwt(Map<String,String> map){
//时间对象
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,3600);//默认一小时过期
//获取Builder对象,目的将集合中的数据,都存储到Jwt的载荷中
JWTCreator.Builder builder = JWT.create();
//遍历并赋值
map.forEach( (k,v) -> { builder.withClaim(k,v);});
//获取Jwt
String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(signature));
//返回
return token;
}
public static DecodedJWT verifyJwt(String token){
return JWT.require(Algorithm.HMAC256(signature)).build().verify(token);
}
}
4.5 jjwt
JJWT的是在JVM上创建和验证JSON Web Token(JWTs)的库,基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
1.导入依赖
<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>
2.运行Jwt工具类,访问 https://jwt.io/ 测试
package com.qf.jwt;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
public class JwtHelper {
// 生成Jwt
public static String jwsWithHS(SignatureAlgorithm signatureAlgorithm, String userInfo, int expire, String secret) {
Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
Claims claims = Jwts.claims();
claims.setSubject(userInfo); // jwt的信息的本身
claims.setExpiration(DateUtils.addSeconds(new Date(), expire)); //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) {
//通过密文生成jwt,然后可以去官网进行校验
String jwtCode = jwsWithHS(SignatureAlgorithm.HS256, "张三", 600, "202cb962ac59075b964b07152d234b70");
System.out.println(jwtCode);
//使用生成的jwtCode进行校验
Jwt jwt = verifySign(
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLlvKDkuIkiLCJleHAiOjE2MTAwMzA2ODB9.stYoxYmkoQRtpS0JADo2gOGhPazJOWe50wovZv9CBBI",
"202cb962ac59075b964b07152d234b70", SignatureAlgorithm.HS256);
//通过Jwt对象获取信息
System.out.println(jwt);
System.out.println(jwt.getBody());//用户信息
}
}
4.6 使用Jwt鉴权校验
1.在springcloud-alibaba-microservice-gateway-9090工程创建鉴权过滤器
package com.qf.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.HttpStatus;
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.util.HashMap;
import java.util.Map;
/**
* 鉴权过滤
*/
@Component // 让AuthFilter 加入到容器中
public class AuthFilter implements GlobalFilter, Ordered {
@Value("${jwt.signature-algorithm}")
private String signatureAlgorithm;//HS256算法
@Value("${jwt.secret}")
private String secret;//密文
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 如果是登陆接口不拦截
if (exchange.getRequest().getPath().toString().contains("login")) {
// 放行 不拦截
return chain.filter(exchange);
}
//获取jwt
String token = exchange.getRequest().getHeaders().getFirst("token");
//判断token
if (token == null) {//进行拦截
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> map = new HashMap<>();
map.put("code", 401);
map.put("message", "未登录");
try {
// 将信息转换为 JSON
ObjectMapper objectMapper = new ObjectMapper();
byte[] data = objectMapper.writeValueAsBytes(map);
// 输出错误信息到页面
DataBuffer buffer = response.bufferFactory().wrap(data);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 让浏览器解析为json
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
// 放行 不拦截
return chain.filter(exchange);
}
/**
* order 决定者当前 Filer 执行顺序,数值愈小,越优先, Filter 越优先执行
*/
@Override
public int getOrder() {
// 所有过滤器执行完了 当前过滤器才再次执行
return 1;
}
}
2.在springcloud-alibaba-microservice-gateway-9090工程中application.yml添加配置:
jwt:
secret: 21232f297a57a5a743894a0e4a801fc3
signature-algorithm: HS256
3.在springcloud-alibaba-microservice-commons工程中创建JsonResult实体类
package com.qf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult {
private Integer code;
private String msg;
private Object data;
}
4.创建子工程springcloud-alibaba-microservice-authentic-6060并添加依赖
<?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>springcloud-alibaba-microservice-manager</artifactId>
<groupId>com.qf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-alibaba-microservice-authentic-6060</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloud-alibaba-microservice-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR6</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
5.在springcloud-alibaba-microservice-authentic-6060工程中配置application.yml
server:
port: 6060
spring:
zipkin:
base-url: http://localhost:9411/
discovery-client-enabled: false
sleuth:
sampler:
# 抽样率 100%, 默认10%
probability: 0.2
application:
# 注册到注册中心的服务名
name: authentic
cloud:
nacos:
discovery:
# 开启nacos的服务发现
enabled: true
server-addr: 127.0.0.1:8848,127.0.0.1:8849
redis:
host: localhost
port: 6379
jwt:
secret: 21232f297a57a5a743894a0e4a801fc3
signature-algorithm: HS256
6.在springcloud-alibaba-microservice-authentic-6060工程中创建启动类
package com.qf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Authentic {
public static void main(String[] args) {
SpringApplication.run(Authentic.class,args);
}
}
7.在springcloud-alibaba-microservice-authentic-6060工程中创建LoginController
package com.qf.controller;
import com.qf.jwt.JwtHelper;
import com.qf.pojo.JsonResult;
import com.qf.redis.RedisPrefix;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/auth")
public class LoginController {
@Value("${jwt.signature-algorithm}")
private String signatureAlgorithm;
@Value("${jwt.secret}")
private String secret;
@Autowired
private RedisTemplate redisTemplate;//保存token到redis,用于换设备登录校验(此处省略未写)
@RequestMapping("/login")
public JsonResult login(String username, String password, HttpServletResponse response) {
JsonResult jsonResult = new JsonResult();
if("jack".equals(username) && "123".equals(password)) {
//使用系统变量存储JWT的获取方式
//String secret = System.getenv().get("JWT_SECRET");
// 生成token
String jwtString = JwtHelper.jwsWithHS(SignatureAlgorithm.forName(signatureAlgorithm), username, 1200, secret);
response.addHeader("token", jwtString);
//保存到redis中,更换密文时,需要比对
//redisTemplate.opsForValue().set(RedisPrefix.AuthenticRedisPrefix.USERTOKEN+"jack",jwtString);
jsonResult.setCode(200);
jsonResult.setMsg("success");
jsonResult.setData(jwtString);
}else{
jsonResult.setCode(500);
jsonResult.setMsg("error");
jsonResult.setData("user is invalid");
}
return jsonResult;
}
}
启动并测试:http://localhost:9090/feign/findAll?origin=qwer 由于Header中未携带tonken则无法访问
再次访问:http://localhost:6060/auth/login?username=jack&password=123 获取token后再次携带token访问
令牌桶算法和漏桶算法
这两种算法的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。