【微服务架构实践学习记录V1】


一、微服务架构的演进过程


1、单体架构

在这里插入图片描述

概念

  1. 整个业务功能写在一个工程中。

优点: 开发、部署、上线非常简单
缺点: 代码耦合严重、牵一发而动全身

2、垂直应用架构

概念

  1. 从单体应用按照功能业务垂直拆分成多个应用。
  2. 每个应用都会独立部署。
  3. 每个应用都有独立的数据库。
  4. 每个应用有自己独立的域名。

优点: 服务、部署独立,水平扩展容易
缺点: 搭建复杂,服务之间关系错综复杂,维护困难

3、SOA架构

在这里插入图片描述

概念

  1. 将耦合的系统划分为面向业务的粗粒度、松耦合、无状态的服务。
  2. 一组互相依赖的服务就构建成了SOA的一个应用系统。
  3. ESB称为企业服务总线,连接各个结点集成不同系统不同协议的服务。

优点: 提供底层服务的统一入口,简化客户端调用
缺点: 实现难度搞,开发周期长,业务逻辑划分太大,存在一定的耦合

4、微服务架构

在这里插入图片描述

概念

  1. 微服务的入口是服务网关,实现对请求的过滤与转发。
  2. 请求会转发到对应的微服务。
  3. 微服务是一个从独立的应用系统中拆分出来的独立服务,独立部署。
  4. 微服务之间使用http进行通信,使用同一个数据库、缓存、消息队列等等资源。
  5. 图中最右侧:实现互通依赖特定组件、中间件。
    服务注册、服务发现:实现对微服务实例的管理与监控,同时微服务之间也知道互相的存在。
    服务配置:将各个微服务的需要动态配置的内容提取出来,实现统一管理和动态配置。
    服务通信:应用于服务之间的互通。
    消息驱动:通过消息队列的形式,服务之间的通信,异步通信,实时性不高的通信。
    熔断限流:发生问题的时候能及时中断请求。
    分布式日志追踪:我们的请求可能会跨多个微服务,查看完整的日志链路。
    分布式事务:每一个微服务都有一个本地事务,多个微服务之间的协同合作成为分布式事务。

优点:
1、单个的微服务,可以选择一门你擅长的语言去开发,扩展性强。
2、对于整个应用而言,代码不再耦合,不会出现大量的冲突。
3、对于整个应用而言,代码不再耦合,不会出现大量的冲突。
4、通过故障隔离,让错误在微服务中降级,不会影响到整个应用(或其他服务)。

缺点: 处理故障难度高、部署工作量大、测试复杂度高、发布风险高、分布性系统问题


二、微服务架构需要遵循的原则


1、不遵循原则将会导致的问题

在这里插入图片描述

1、没有提供网关,导致客户端需要记住所有的微服务,而且需要在客户端实现负载均衡。
2、微服务之间的依赖错综复杂,难以维护
3、开发过程[互相纠缠],开发、上线时间严重影响

2、原则一:职责独立

在这里插入图片描述

概念:每个微服务只做自己功能范围内的事,微服务之间的依赖链不要过长。
功能都是独立的,每个微服务功能不应该交叉,否则需要修改微服务的功能实现划分。
以上图片依赖链不能超过3,A-E的设计是不合理的,排错复杂。

3、原则二:熔断器实现快速的故障容错和线程隔离

概念:使得单个微服务出错后,错误不向下传播,以至于整个系统的瘫痪。
例如:Hystrix、Sentinel

4、原则三:通过网关代理微服务请求

在这里插入图片描述

概念:网关是微服务架构对外暴露的唯一入口。
优势:整个系统的入口只有一个,方便流量请求控制过滤。拥有高性能同时,可满足流量的并发控制

4、原则四:确保微服务API变更后能够向后兼容

概念:修改接口时需要兼容,提供默认参数等等


三、认识领域驱动设计(DDD)


1、DDD的概念

  1. DDD是一种软件架构设计方法,它并不定义软件开发过程(DevOps)
  2. DDD利用面向对象的特性,以业务为核心驱动,而不是传统的数据库(表)驱动开发
  3. 传统功能开发是先设计数据表,DDD把业务思想摆在首位,业务就是领域

2、领域的概念

  1. 领域是对功能需求的划分;大的领域下面还有许多小的子领域
    在这里插入图片描述

3、领域建模

  1. 分析领域模型,推演实体(数据表)、值对象(服务之间传递数据的对象)、领域服务(具体功能实现)
  2. 找出聚合边界(降低服务合) 合理的业务和服务拆分
  3. 为聚合配备存储仓库(数据持久化)
  4. 实践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 时效性完全由服务端的逻辑说了算
  • 30
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值