文章目录
一、微服务架构的演进过程
1、单体架构
概念
- 整个业务功能写在一个工程中。
优点: 开发、部署、上线非常简单
缺点: 代码耦合严重、牵一发而动全身
2、垂直应用架构
概念
- 从单体应用按照功能业务垂直拆分成多个应用。
- 每个应用都会独立部署。
- 每个应用都有独立的数据库。
- 每个应用有自己独立的域名。
优点: 服务、部署独立,水平扩展容易
缺点: 搭建复杂,服务之间关系错综复杂,维护困难
3、SOA架构
概念
- 将耦合的系统划分为面向业务的粗粒度、松耦合、无状态的服务。
- 一组互相依赖的服务就构建成了SOA的一个应用系统。
- ESB称为企业服务总线,连接各个结点集成不同系统不同协议的服务。
优点: 提供底层服务的统一入口,简化客户端调用
缺点: 实现难度搞,开发周期长,业务逻辑划分太大,存在一定的耦合
4、微服务架构
概念
- 微服务的入口是服务网关,实现对请求的过滤与转发。
- 请求会转发到对应的微服务。
- 微服务是一个从独立的应用系统中拆分出来的独立服务,独立部署。
- 微服务之间使用http进行通信,使用同一个数据库、缓存、消息队列等等资源。
- 图中最右侧:实现互通依赖特定组件、中间件。
服务注册、服务发现:实现对微服务实例的管理与监控,同时微服务之间也知道互相的存在。
服务配置:将各个微服务的需要动态配置的内容提取出来,实现统一管理和动态配置。
服务通信:应用于服务之间的互通。
消息驱动:通过消息队列的形式,服务之间的通信,异步通信,实时性不高的通信。
熔断限流:发生问题的时候能及时中断请求。
分布式日志追踪:我们的请求可能会跨多个微服务,查看完整的日志链路。
分布式事务:每一个微服务都有一个本地事务,多个微服务之间的协同合作成为分布式事务。
优点:
1、单个的微服务,可以选择一门你擅长的语言去开发,扩展性强。
2、对于整个应用而言,代码不再耦合,不会出现大量的冲突。
3、对于整个应用而言,代码不再耦合,不会出现大量的冲突。
4、通过故障隔离,让错误在微服务中降级,不会影响到整个应用(或其他服务)。
缺点: 处理故障难度高、部署工作量大、测试复杂度高、发布风险高、分布性系统问题
二、微服务架构需要遵循的原则
1、不遵循原则将会导致的问题
1、没有提供网关,导致客户端需要记住所有的微服务,而且需要在客户端实现负载均衡。
2、微服务之间的依赖错综复杂,难以维护
3、开发过程[互相纠缠],开发、上线时间严重影响
2、原则一:职责独立
概念:每个微服务只做自己功能范围内的事,微服务之间的依赖链不要过长。
功能都是独立的,每个微服务功能不应该交叉,否则需要修改微服务的功能实现划分。
以上图片依赖链不能超过3,A-E的设计是不合理的,排错复杂。
3、原则二:熔断器实现快速的故障容错和线程隔离
概念:使得单个微服务出错后,错误不向下传播,以至于整个系统的瘫痪。
例如:Hystrix、Sentinel
4、原则三:通过网关代理微服务请求
概念:网关是微服务架构对外暴露的唯一入口。
优势:整个系统的入口只有一个,方便流量请求控制过滤。拥有高性能同时,可满足流量的并发控制
4、原则四:确保微服务API变更后能够向后兼容
概念:修改接口时需要兼容,提供默认参数等等
三、认识领域驱动设计(DDD)
1、DDD的概念
- DDD是一种软件架构设计方法,它并不定义软件开发过程(DevOps)
- DDD利用面向对象的特性,以业务为核心驱动,而不是传统的数据库(表)驱动开发
- 传统功能开发是先设计数据表,DDD把业务思想摆在首位,业务就是领域
2、领域的概念
- 领域是对功能需求的划分;大的领域下面还有许多小的子领域
3、领域建模
- 分析领域模型,推演实体(数据表)、值对象(服务之间传递数据的对象)、领域服务(具体功能实现)
- 找出聚合边界(降低服务合) 合理的业务和服务拆分
- 为聚合配备存储仓库(数据持久化)
- 实践DDD,并不断推倒和重构
四、电商工程业务解读及微服务模块拆分
1、业务划分
2、微服务划分
1、网关是微服务架构的唯一入口
2、四大功能微服务模块:账户、商品、订单、物流
五、创建电商工程
1、父工程创建
父工程引入pom文件依赖
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.imooc.ecommerce</groupId>
<artifactId>ecommerce-springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 子模块 -->
<modules>
<module>e-commerce-common</module>
<module>e-commerce-mvc-config</module>
<module>e-commerce-alibaba-nacos-client</module>
<module>e-commerce-admin</module>
<module>e-commerce-authority-center</module>
<module>e-commerce-gateway</module>
<module>e-commerce-service</module>
<module>e-commerce-hystrix-dashboard</module>
<module>e-commerce-stream-client</module>
</modules>
<!-- 给其他子模块提供pom依赖的,所以不能是jar -->
<packaging>pom</packaging>
<!-- springcloud是从springboot开始开发的 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
<!-- 属性配置,springcloud和springcloud alibaba需要适配,否则会出错 -->
<properties>
<!-- Spring Cloud Hoxton.SR8 依赖 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 依赖 -->
<spring-cloud-alibaba.version>2.2.4.RELEASE</spring-cloud-alibaba.version>
</properties>
<!-- 通用依赖都放在主工程下面,子工程会自动继承以下依赖 -->
<dependencies>
<!-- lombok 工具通过在代码编译时期动态的将注解替换为具体的代码,
IDEA 需要添加 lombok 插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope><!-- 测试环境才生效-->
</dependency>
<!-- 健康检查,安全检查,对应用系统做监控使用的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 像token工具类的依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<!-- 集合工具类的依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.0</version>
</dependency>
<!-- 引入jwt,用于鉴权-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<!-- json序列化工具-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<!-- 项目依赖管理 父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) -->
<dependencyManagement>
<dependencies>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud alibaba 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 配置远程仓库,maven中配置的仓库可能找不到依赖,根据这里配置的仓库寻找依赖 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
2、通用工程创建
1、通用工程导入pom依赖
<parent>
<!-- 继承父工程 -->
<artifactId>ecommerce-springcloud</artifactId>
<groupId>com.imooc.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- Maven版本号 -->
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-common</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-common</name>
<description>通用模块</description>
2、创建通用响应对象
/**
* <h1>通用响应对象定义</h1>
* {
* "code": 0,
* "message": "",
* "data": {}
* }
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> implements Serializable {
/** 错误码 */
private Integer code;
/** 错误消息 */
private String message;
/** 泛型响应数据 */
private T Data;
public CommonResponse(Integer code, String message) {
this.code = code;
this.message = message;
}
}
3、通用配置工程创建
1、通用工程导入pom依赖
<parent>
<artifactId>ecommerce-springcloud</artifactId>
<groupId>com.imooc.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-mvc-config</name>
<description>通用配置模块</description>
<dependencies>
<!-- 引入 Web 功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.imooc.ecommerce</groupId>
<artifactId>e-commerce-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2、定义注解类
/**
* <h1>忽略统一响应注解定义,允许不反回统一响应</h1>
* ElementType.TYPE : 可标识到类
* ElementType.METHOD : 可标识到方法
* RetentionPolicy.RUNTIME : 保留到运行时都要生效
* */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
}
3、创建通知aop(统一响应)
/**
* <h1>实现统一响应</h1>
* */
//在RestController中实现对ResponseBody的拦截
//在com.imooc.ecommerce下生效
@RestControllerAdvice(value = "com.imooc.ecommerce")
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {
/**
* <h2>判断是否需要对响应进行处理,判断是否需要进行包装</h2>
* */
@Override
@SuppressWarnings("all") //屏蔽警告信息
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
if (methodParameter.getDeclaringClass() //获取方法所在的类是否有忽略统一响应注解标识
.isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
if (methodParameter.getMethod() //获取方法是否有忽略统一响应注解标识
.isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
return true;
}
/**
* <h3>上面判断为true后,包装响应信息</h3>
* */
@Override
@SuppressWarnings("all")
public Object beforeBodyWrite(Object o, MethodParameter methodParameter,
MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 定义最终的返回对象
CommonResponse<Object> response = new CommonResponse<>(0, "");
if (null == o) {//空则不包装
return response;
} else if (o instanceof CommonResponse) { //原本是CommonResponse就不包装
response = (CommonResponse<Object>) o;
} else {
response.setData(o);
}
return response;
}
}
4、创建通知aop(全局异常统一捕获处理)
/**
* <h2>全局异常捕获处理</h2>
* */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = Exception.class) //对系统中所有的异常进行拦截并返回统一类型,捕获Exception异常
public CommonResponse<String> handlerCommerceException(
HttpServletRequest req, Exception ex
) {
CommonResponse<String> response = new CommonResponse<>(
-1, "business error"
);
response.setData(ex.getMessage());
log.error("commerce service has error: [{}]", ex.getMessage(), ex);
return response;
}
}
六、Alibaba Nacos
1、基本架构
1、服务 : 一个或一组软件功能,目的是不同的客户端可以为不同的目的重用,通过跨进程的网络调用。如spirngcloud resful。
2、 service 配置服务 : 工程在运行过程中的动态配置信息。
3、 名字服务 :服务注册(ip地址、端口号、服务名称),服务发现(微服务提供信息通过nacos寻找其他微服务)。
4、nacos console :可视化页面查看实例信息,配置数据等等
2、概念
- 服务注册中心 : 服务,实例及元数据的数据库,他会存储这些数据,服务注册中心可能会调用服务实例的健康检查API来验证它是否能够处理请求(是否存活)。
- 服务元数据 : 包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据(其实就是描述服务自身的数据)
- 服务提供、消费方 : 提供可复用和可调用服务的应用方,会发起对某个服务调用的应用方。
- 配置 : 在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理以独立的配置文件的形式存在。
3、单机版本部署步骤
-
1、下载你所需要的版本:https://github.com/alibaba/nacos/releases
-
2、解压: tar -xzvfnacos-server-2.0.0.tar.gz
-
3、单机模式启动(默认配置就可以):./startup.sh -m standalone (standalone:单机)
-
4、关注点application.properties
-
5、127.0.0.1:8848/nacos 进行访问
-
6、登录名、登录秘密默认为:nacos
-
7、服务列表:当前已注册的服务,提供服务注册、服务发现等功能
-
8、命名空间:可创建多个命名空间对应不用的工程应用,比如电商工程、物流工程等,分别有单独属于自己命名空间的配置所在的数据表名为tenant_info
-
9、默认启动时:数据都会保存到内嵌的数据库,存储有上限,不支持集群化部署。
-
10、给Nacos配置自定义的 MySQL 持久化,修改配置,指定 MySQL地址、用户名、端口号
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB: 0代表第一个数据库
db.url.0=jdbc:mysgl://127.0,0,1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000
db.user.0=rootdb.password.0=root
- 11、创建数据库与数据表 使用conf的nacos-mysql.sql(mysql数据库)、schema.sql执行,config_info为配置列表中的内容
- 12、重启nacos
4、集群化部署步骤
- 1、定义集群部署的ip 和端口,即 cluster.conf 文件
- 2、集群必须要使用可以共同访问(例如 MySQL、PG等等)到的数据源作为持久化的方式
- 3、复制三个nacos文件
- 4、将复制的nacos文件中的application.properties的端口号修改为对应端口号
- 5、集群化启动没有额外的参数:./startup.sh,进入每个文件夹里面执行文件
- 6、游览器查看
- 7、有节点挂了之后不影响整个服务,可查看日志,在logs文件夹中start.out
5、服务注册与发现
上面时nacos集群,下面时微服务
1、创建概念验证微服务(验证实不实用,好不好用))测试可用性
pom文件内容
<parent>
<artifactId>ecommerce-springcloud</artifactId>
<groupId>com.imooc.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-alibaba-nacos-client</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-alibaba-nacos-client</name>
<description>Nacos Client</description>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 注册与发现使用 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.imooc.ecommerce</groupId>
<artifactId>e-commerce-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 配置远程仓库 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、工程启动入口
/**
* <h1>Nacos Client 工程启动入口</h1>
* */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosClientApplication {
public static void main(String[] args) {
SpringApplication.run(NacosClientApplication.class, args);
}
}
3、工程配置
server:
port: 8000
servlet:
context-path: /ecommerce-nacos-client
spring:
application:
name: e-commerce-nacos-client # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
cloud:
nacos:
# 服务注册发现
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
# 命名空间
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
# 暴露端点 对应父工程的spring-boot-starter-actuator依赖
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
4、启动应用
5、服务发现代码实现
@Slf4j
@Service
public class NacosClientService {
//构造器方式依赖注入、springcloud中而不是alibaba中的,springcloud只提供接口,具体实现靠的是第三方
private final DiscoveryClient discoveryClient;
public NacosClientService(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
/**
* <h2>打印 Nacos Client 信息到日志中</h2>
* */
public List<ServiceInstance> getNacosClientInfo(String serviceId) {
log.info("request nacos client to get service instance info: [{}]", serviceId);
return discoveryClient.getInstances(serviceId);
}
}
/**
* <h1>nacos client controller</h1>
* */
@Slf4j
@RestController
@RequestMapping("/nacos-client")
public class NacosClientController {
private final NacosClientService nacosClientService;
public NacosClientController(NacosClientService nacosClientService) {
this.nacosClientService = nacosClientService;
}
/**
* <h2>根据 service id 获取服务所有的实例信息</h2>
* */
@GetMapping("/service-instance")
public List<ServiceInstance> logNacosClientInfo(
@RequestParam(defaultValue = "e-commerce-nacos-client") String serviceId) {
log.info("coming in log nacos client info: [{}]", serviceId);
return nacosClientService.getNacosClientInfo(serviceId);
}
}
返回值
### 查询服务实例信息
GET http://127.0.0.1:8000/ecommerce-nacos-client/nacos-client/service-instance
Accept: application/json
> { "code":0,
"message" :"",
"data":[
{"serviceId":"e-commerce-nacos-client"
"host":"192.168.1.2"
"port": 8000,
"secure": false ,
"metadata":{
"nacos.instanceId":"192.168,1,2#8000#DEFAULT#DEFAULT_GROUP@e-commerce-nacos-client"
"nacos.weight":"1.0"
"nacos.cluster":"DEFAULTT"
"nacos.ephemeral":"true"
"nacos.healthy","true"
"preserved.register.source":"SPRING_CLOUD"
}
"url":"http://192.168 12 8000",
"instanceld": null,
"scheme":null
}
]
}
七、Admin 监控服务
1、springboot actuator
- 作用:可查看应用系统的详细信息,自动化配置,beans,环境属性等信息
- 核心部分:Endpoint端点,它用来监视应用程序及交互:SpringBoot Actuator 内置了很多 Endpoints,并支持扩展,都是接口返回json数据。需要自己实现解析,非常耗时。
- SpringBoot Actuator 提供的原生端点有三类
应用配置类:自动配置信息、Spring Bean 信息yml文件信息、环境信息等等
度量指标类:主要是运行期间的动态信息,例如堆栈、健康指标、metrics 信息等等
操作控制类:主要是指shutdown,用户可以发送一个请求将应用的监控功能关闭
2、搭建监控服务器步骤
对springboot actuator进行封装和解释
1、添加 SpringBoot Admin Starter 自动配置依赖
<!-- SpringBoot Admin -->
<!-- 1.spring-boot-admin-server : Server 端
2.spring-boot-admin-server-ui : UI 界面
3.spring-boot-admin-server-cloud :对 Spring Cloud 的接入 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.0</version>
</dependency>
2、添加启动注解:@EnableAdminServer
3、监控服务器代码配置
1、pom文件配置
<parent>
<artifactId>ecommerce-springcloud</artifactId>
<groupId>com.imooc.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-admin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-admin</name>
<description>监控服务器</description>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- SpringBoot Admin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.imooc.ecommerce</groupId>
<artifactId>e-commerce-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、启动类配置
/**
* <h1>监控中心服务器启动入口</h1>
* */
@EnableAdminServer
@SpringBootApplication
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
3、配置文件
server:
port: 7001
servlet:
context-path: /e-commerce-admin
spring:
application:
name: e-commerce-admin
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
thymeleaf:
check-template: false
check-template-location: false
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 *, 可以开放所有端点
endpoint:
health:
show-details: always
4、启动访问
4、应用注册到 Admin Server
被监控和管理的应用(微服务),注册到Admin Server 的两种方式
- 方式一:被监控和管理的应用程序,使用 SpringBoot Admin Client 库,通过 HTTP调用注册到 SpringBoot Admin Server上
- 方式二:首先,被监控和管理的应用程序,注册到 SpringCloud 集成的注册中心;然后SpringBootAdmin Server 通过注册中心获取到被监控和管理的应用程序
以上代码配置方式使用的是方式二,通过配置nacos与暴露端点,使项目可访问到actuator接口。
5、添加安全访问控制
1、pom文件加入
<!-- 开启登录认证功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、配置文件加入
spring:
application:
name: e-commerce-admin
#新加入配置
security:
user:
name: imooc-qinyi
password: 88888888
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
#新加入配置
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
user.name: imooc-qinyi
user.password: 88888888
3、加入配置安全认证访问
/**
* <h1>配置安全认证, 以便其他的微服务可以注册</h1>
* 为了保证其他微服务能够访问到actuator端点
* 能够从nacos获取安全的实例信息需要做安全的配置信息
* 参考 Spring Security 官方
* */
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
/** 应用上下文路径,server.servlet.context-path: /e-commerce-admin */
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//授权成功的handler
SavedRequestAwareAuthenticationSuccessHandler successHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
//重定向
successHandler.setTargetUrlParameter("redirectTo");
//默认的url
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
// 1. 配置所有的静态资源和登录页可以公开访问
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
// 2. 其他请求, 必须要经过认证
.anyRequest().authenticated()
.and()
// 3. 配置登录和登出路径
.formLogin().loginPage(adminContextPath + "/login")
.successHandler(successHandler)
.and()
.logout().logoutUrl(adminContextPath + "/logout")
.and()
// 4. 开启 http basic 支持, 其他的服务模块注册时需要使用
.httpBasic()
.and()
// 5. 开启基于 cookie 的 csrf 保护
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// 6. 忽略这些路径的 csrf 保护以便其他的模块可以实现注册
.ignoringAntMatchers(
adminContextPath + "/instances",
adminContextPath + "/actuator/**"
);
}
}
4、游览访问
6、监控自定义告警
方式一、邮箱配置
1、pom文件配置
<!-- 实现对 Java Mail 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2、配置文件加入
# 被监控的应用状态变更为 DOWN(不知原因)、OFFLINE(主动下线)、UNKNOWN(不知道状态) 时,
# 会自动发出告警: 实例的状态、原因、实例地址等信息
# 需要在 pom.xml 文件中添加 spring-boot-starter-mail 依赖
# 配置发送告警的邮箱服务器
# 但是, 这个要能连接上, 否则会报错
mail:
host: qinyi.imooc.com
username: qinyi@imooc.com
password: QinyiZhang
default-encoding: UTF-8
# 监控告警通知
boot:
admin:
notify:
mail:
from: ${spring.mail.username}
to: qinyi@imooc.com
cc: qinyi@imooc.com
方式二、事件通知器实现
1、代码配置
/**
* <h1>自定义告警</h1>
* */
@Slf4j
@Component
@SuppressWarnings("all")
public class QinyiNotifier extends AbstractEventNotifier {
protected QinyiNotifier(InstanceRepository repository) {
super(repository);
}
/**
* <h2>实现对事件的通知</h2>
* */
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
//判断实例状态是否发生改变
if (event instanceof InstanceStatusChangedEvent) {
log.info("Instance Status Change: [{}], [{}], [{}]",
instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());
} else {
log.info("Instance Info: [{}], [{}], [{}]",
instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
}
2、执行结果
# admin启动时
Instance Status Change: [e-commerce-admin],[55b1ee141192],[UP]
Instance Info: [e-commerce-admin],[55b1ee141192],[ENDPOINTS_DETECTED]#已被监控
#启动概念验证微服务
Instance Info: [e-commerce-nacos-client],[31805aa1c983],[REGISTERED]#注册
Instance Status Change: [e-commerce-nacos-client],[31805aa1c983],[UP]
Instance Info: [e-commerce-nacos-client],31805aa1c983],[ENDPOINTS_DETECTED]#已被监控
#关闭概念验证微服务
InstanceStatus Change: [e-commerce-nacos-client],[31805aa1c983],[OFFLINE] #主动下线
Instance '31805aa1c983' missing in DiscoveryClient services and will be removed
Instance Info: [e-commerce-nacos-client],[31805aa1c983],[EREGISTERED] #取消注册
八、授权、鉴权中心微服务
1、授权、鉴权中心微服务功能设计
1.1 JWT的基本概念
- JSON Web Token (JWT)是一个开放标准它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息使用场景:用户授权、信息交换
1.2 JWT的结构(组成部分)
- JWT由三个部分组成:Header、Payload、Signature,且用圆点连接 xxxxx.yyyyy.zzzzz
- Header:由两部分(Token 类型、加密算法名称)组成,并使用 Base64 编码
- Payload:KV形式的数据,即你想传递的数据(授权的话就是Token 信息)
- Signature:为了得到签名部分,你必须有编码过的 Header、编码过的payload、一个秘钥签名算法是Header中指定的那个,然对它们签名即可 HMACSHA256(base64UrlEncode(header) +“.”+ base64UrlEncode(payload),secret)
1.3 授权、鉴权中心微服务功能逻辑架构
鉴权不放微服务中,只是一个java文件
2、搭建授权、鉴权中心微服务
2.1、pom文件
<parent>
<artifactId>ecommerce-springcloud</artifactId>
<groupId>com.imooc.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-authority-center</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-authority-center</name>
<description>授权中心</description>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- Java Persistence API, ORM 规范 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.imooc.ecommerce</groupId>
<artifactId>e-commerce-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<!-- screw 生成数据库文档 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
2.2、yml文件配置
server:
port: 7000
servlet:
context-path: /ecommerce-authority-center
spring:
application:
name: e-commerce-authority-center
cloud:
nacos:
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.show_sql: true
hibernate.format_sql: true
open-in-view: false
datasource:
# 数据源
url: jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: ImoocEcommerceHikariCP
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
2.3、启动类
/**
* <h1>授权中心启动入口</h1>
* */
@EnableDiscoveryClient
@SpringBootApplication
public class AuthorityCenterApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorityCenterApplication.class, args);
}
}
3、数据表及ORM过程
3.1、数据表
-- 创建 t_ecommerce_user 数据表
CREATE TABLE IF NOT EXISTS `imooc_e_commerce`.`t_ecommerce_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(256) NOT NULL DEFAULT '' COMMENT 'MD5 加密之后的密码',
`extra_info` varchar(1024) NOT NULL DEFAULT '' COMMENT '额外的信息',
`create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户表';
3.2、实体类创建
/**
* <h1>用户表实体类定义</h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class) //自动创建时间
@Table(name = "t_ecommerce_user")
public class EcommerceUser implements Serializable {
/** 自增主键 */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
/** 用户名 */
@Column(name = "username", nullable = false)
private String username;
/** MD5 密码 */
@Column(name = "password", nullable = false)
private String password;
/** 额外的信息, json 字符串存储 */
@Column(name = "extra_info", nullable = false)
private String extraInfo;
/** 创建时间 */
@CreatedDate //自动创建时间
@Column(name = "create_time", nullable = false)
private Date createTime;
/** 更新时间 */
@LastModifiedDate //自动创建更新时间
@Column(name = "update_time", nullable = false)
private Date updateTime;
}
//启动类加入@EnableJpaAuditing允许 Jpa 自动审计
3.3、dao层实现
/**
* <h1>EcommerceUser Dao 接口定义</h1>
* */
public interface EcommerceUserDao extends JpaRepository<EcommerceUser, Long> {
/**
* <h2>根据用户名查询 EcommerceUser 对象</h2>
* select * from t_ecommerce_user where username = ?
* */
EcommerceUser findByUsername(String username);
/**
* <h2>根据用户名和密码查询实体对象</h2>
* select * from t_ecommerce_user where username = ? and password = ?
* */
EcommerceUser findByUsernameAndPassword(String username, String password);
}
4、生成 RSA256 公钥和私钥对
4.1、测试生成RSA公钥私钥对
/**
* <h1>RSA 非对称加密算法: 生成公钥和私钥</h1>
* */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class RSATest {
@Test
public void generateKeyBytes() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
// 生成公钥和私钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥和私钥对象
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
log.info("private key: [{}]", Base64.encode(privateKey.getEncoded()));
log.info("public key: [{}]", Base64.encode(publicKey.getEncoded()));
}
}
4.2、私钥常量类
/**
* <h1>授权需要使用的一些常量信息</h1>
* */
public final class AuthorityConstant {
/** RSA 私钥, 除了授权中心以外, 不暴露给任何客户端 */
public static final String PRIVATE_KEY = "";
/** 默认的 Token 超时时间, 一天 */
public static final Integer DEFAULT_EXPIRE_DAY = 1;
}
4.3、公钥常量类
/**
* <h1>通用模块常量定义</h1>
* */
public final class CommonConstant {
/** RSA 公钥 */
public static final String PUBLIC_KEY = "生成的公钥";
/** JWT 中存储用户信息的 key */
public static final String JWT_USER_INFO_KEY = "e-commerce-user";
/** 授权中心的 service-id */
public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authority-center";
}
4.4、创建用户名密码对象
/**
* <h1>用户名和密码</h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UsernameAndPassword {
/** 用户名 */
private String username;
/** 密码 */
private String password;
}
4.5、Token
/**
* <h1>授权中心鉴权之后给客户端的 Token 方便扩展</h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtToken {
/** JWT */
private String token;
}
4.6、登录用户对象
/**
* <h1>登录用户信息</h1>
* 已经登录的用户信息,从token解析
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserInfo {
/** 用户 id */
private Long id;
/** 用户名 */
private String username;
}
5、基于 JWT + RSA256 的授权
5.1、定义jwt服务接口
/**
* <h1>JWT 相关服务接口定义</h1>
* */
public interface IJWTService {
/**
* <h2>生成 JWT Token, 使用默认的超时时间</h2>
* */
String generateToken(String username, String password) throws Exception;
/**
* <h2>生成指定超时时间的 Token, 单位是天</h2>
* */
String generateToken(String username, String password, int expire) throws Exception;
/**
* <h2>注册用户并生成 Token 返回</h2>
* */
String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword)
throws Exception;
}
/**
* <h1>JWT 相关服务接口实现</h1>
* */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class JWTServiceImpl implements IJWTService {
private final EcommerceUserDao ecommerceUserDao;
public JWTServiceImpl(EcommerceUserDao ecommerceUserDao) {
this.ecommerceUserDao = ecommerceUserDao;
}
@Override
public String generateToken(String username, String password) throws Exception {
return generateToken(username, password, 0);
}
@Override
public String generateToken(String username, String password, int expire)
throws Exception {
// 首先需要验证用户是否能够通过授权校验, 即输入的用户名和密码能否匹配数据表记录
EcommerceUser ecommerceUser = ecommerceUserDao.findByUsernameAndPassword(
username, password
);
if (null == ecommerceUser) {
log.error("can not find user: [{}], [{}]", username, password);
return null;
}
// Token 中塞入对象, 即 JWT 中存储的信息, 后端拿到这些信息就可以知道是哪个用户在操作
LoginUserInfo loginUserInfo = new LoginUserInfo(
ecommerceUser.getId(), ecommerceUser.getUsername()
);
if (expire <= 0) {
expire = AuthorityConstant.DEFAULT_EXPIRE_DAY;
}
// 计算超时时间
ZonedDateTime zdt = LocalDate.now().plus(expire, ChronoUnit.DAYS)
.atStartOfDay(ZoneId.systemDefault());
Date expireDate = Date.from(zdt.toInstant());
return Jwts.builder()
// jwt payload --> KV
.claim(CommonConstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserInfo))
// jwt id
.setId(UUID.randomUUID().toString())
// jwt 过期时间
.setExpiration(expireDate)
// jwt 签名 --> 加密
.signWith(getPrivateKey(), SignatureAlgorithm.RS256)
.compact();
}
@Override
public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword)
throws Exception {
// 先去校验用户名是否存在, 如果存在, 不能重复注册
EcommerceUser oldUser = ecommerceUserDao.findByUsername(
usernameAndPassword.getUsername());
if (null != oldUser) {
log.error("username is registered: [{}]", oldUser.getUsername());
return null;
}
EcommerceUser ecommerceUser = new EcommerceUser();
ecommerceUser.setUsername(usernameAndPassword.getUsername());
ecommerceUser.setPassword(usernameAndPassword.getPassword()); // MD5 编码以后
ecommerceUser.setExtraInfo("{}");
// 注册一个新用户, 写一条记录到数据表中
ecommerceUser = ecommerceUserDao.save(ecommerceUser);
log.info("register user success: [{}], [{}]", ecommerceUser.getUsername(),
ecommerceUser.getId());
// 生成 token 并返回
return generateToken(ecommerceUser.getUsername(), ecommerceUser.getPassword());
}
/**
* <h2>根据本地存储的私钥获取到 PrivateKey 对象</h2>
* */
private PrivateKey getPrivateKey() throws Exception {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
new BASE64Decoder().decodeBuffer(AuthorityConstant.PRIVATE_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(priPKCS8);
}
}
5.2、控制层创建
/**
* <h1>对外暴露的授权服务接口</h1>
* */
@Slf4j
@RestController
@RequestMapping("/authority")
public class AuthorityController {
private final IJWTService ijwtService;
public AuthorityController(IJWTService ijwtService) {
this.ijwtService = ijwtService;
}
/**
* <h2>从授权中心获取 Token (其实就是登录功能), 且返回信息中没有统一响应的包装</h2>
* */
@IgnoreResponseAdvice
@PostMapping("/token")
public JwtToken token(@RequestBody UsernameAndPassword usernameAndPassword)
throws Exception {
log.info("request to get token with param: [{}]",
JSON.toJSONString(usernameAndPassword));
return new JwtToken(ijwtService.generateToken(
usernameAndPassword.getUsername(),
usernameAndPassword.getPassword()
));
}
/**
* <h2>注册用户并返回当前注册用户的 Token, 即通过授权中心创建用户</h2>
* */
@IgnoreResponseAdvice
@PostMapping("/register")
public JwtToken register(@RequestBody UsernameAndPassword usernameAndPassword)
throws Exception {
log.info("register user with param: [{}]", JSON.toJSONString(
usernameAndPassword
));
return new JwtToken(ijwtService.registerUserAndGenerateToken(
usernameAndPassword
));
}
}
6、基于 JWT + RSA256 的鉴权
token解析类就是单独一个java文件
/**
* <h1>JWT Token 解析工具类</h1>
* */
public class TokenParseUtil {
/**
* <h2>从 JWT Token 中解析 LoginUserInfo 对象</h2>
* */
public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception {
if (null == token) {
return null;
}
Jws<Claims> claimsJws = parseToken(token, getPublicKey());
Claims body = claimsJws.getBody();
// 如果 Token 已经过期了, 返回 null
if (body.getExpiration().before(Calendar.getInstance().getTime())) {
return null;
}
// 返回 Token 中保存的用户信息
return JSON.parseObject(
body.get(CommonConstant.JWT_USER_INFO_KEY).toString(),
LoginUserInfo.class
);
}
/**
* <h2>通过公钥去解析 JWT Token</h2>
* */
private static Jws<Claims> parseToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* <h2>根据本地存储的公钥获取到 PublicKey 对象</h2>
* */
private static PublicKey getPublicKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY)
);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
}
测试:
/**
* <h1>JWT 相关服务测试类</h1>
* */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class JWTServiceTest {
@Autowired
private IJWTService ijwtService;
@Test
public void testGenerateAndParseToken() throws Exception {
String jwtToken = ijwtService.generateToken(
"Qinyi@imooc.com",
"25d55ad283aa400af464c76d713c07ad"
);
log.info("jwt token is: [{}]", jwtToken);
LoginUserInfo userInfo = TokenParseUtil.parseUserInfoFromToken(jwtToken);
log.info("parse token: [{}]", JSON.toJSONString(userInfo));
}
}
7、总结
7.1、基于服务器的身份认证
- 最为传统的做法,客户端存储 Cookie(一般是Session id),服务器存储 Session
- Session是每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大
- 在不同域名之前切换时,请求可能会被禁止,即跨域问题
7.2、基于Token(JWT)的身份认证
- JWT 与Session的差异相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的
- JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力
7.3、对比两种认证
- 解析方法:JWT使用算法直接解析得到用户信息;Session 需要额外的数据映射,实现匹配
- 管理方法:JWT只有过期时间的限制;Session 数据保存在服务器,可控性更强
- 跨平台:JWT 就是一段字符串,可以任意传播;Session跨平台需要有统一的解析平台,较为繁琐
- 时效性:JWT 一旦生成,独立存在,很难做特殊控制,Session 时效性完全由服务端的逻辑说了算