实战指南:使用Gateway、MybatisPlus和Nacos实现登录、JWT身份验证以及信息查询

1.需求分析

本次需要完成的内容是:登录的用户可以获取所有用户相关信息,否则无法获取。本次使用的技术有Gateway,Nacos,MP,Docker,其中使用Knife4j完成对接口文档的快速生成.以及使用Docker容器完成Nacos的部署.

2.安装Docker以及Nacos镜像

在创建SpringBoot项目之前,首先我们先创建虚拟机,在虚拟机中安装Docker以及Nacos的相关镜像.

2.1 安装Docker

2.1.1 卸载Docker

如果之前的虚拟机或者服务器上安装了Docker可以先卸载Docker,再进行本次Docker的安装

1
2
3
4
5
6
7
8
9
10
11
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce

2.1.2 安装Docker

安装Docker之前首先安装yum工具(虚拟机需要联网)

1
2
3
yum install -y yum-utils \
device-mapper-persistent-data \
lvm2 --skip-broken

如果使用上述命令显示您需要 root 权限执行此命令,执行下面命令

1
su - root

安装最后出现complete即为安装成功

然后设置国内镜像源,由于国内用户访问Docker速度非常慢,因此设置阿里云镜像提高访问以及下载Docker相关镜像的速度,执行下面命令:

1
2
3
4
5
6
7
8
# 设置docker镜像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo

yum makecache fast

设置好阿里云镜像源后,开始下载安装Docker

1
yum install -y docker-ce

安装最后出现complete即为安装成功

在正式启动Docker前,首先要关闭虚拟机的防火墙,因为后续使用Docker的过程中,我们需要将容器内的端口和虚拟机的端口进行映射,因此需要关闭虚拟机的防火墙,执行下面命令

1
2
3
4
# 关闭
systemctl stop firewalld
# 禁止开机启动防火墙
systemctl disable firewalld

关闭防火墙后我们可以使用命令开启关闭Docker容器

1
2
3
4
5
systemctl start docker  # 启动docker服务

systemctl stop docker # 停止docker服务

systemctl restart docker # 重启docker服务

可以使用命令查看docker版本

1
docker -v

我的版本是Docker version 24.0.7, build afdd53b

接下来配置镜像加速

docker官方镜像仓库网速较差,我们需要设置国内镜像服务:

参考阿里云的镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors (这里官网有具体的操作)

2.2 下载安装Nacos

使用docker search nacos命令查找nacos镜像,如下图:

这里我们选择的是第一个

然后使用docker pull nacos/nacos-server命令下载镜像,下载完成后使用命令docker images -a查看所有镜像

接下来创建Nacos容器:

1
docker run --env MODE=standalone --name nacos --restart=always  -d -p 8848:8848 nacos/nacos-server

创建并运行容器成功后,访问192.168.200.131:8848/nacos(ip地址:8848/nacos)即可访问nacos官网,如下图,即为运行成功:

输入密码和账号,都为nacos,点击登录后看到下面页面即访问nacos成功

至此,Docker与Nacos安装完成

3.创建SpringBoot项目以及整合配置类

3.1 创建SprigBoot项目

创建SpringBoot项目成功后,创建模块demo-common模块,用于存放本次实战所需要用到的配置类等信息.

3.2 创建demo-common模块

创建完成demo-common模块后,新建Knife4jConfig配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.xju.edu.config;

import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Slf4j
@Configuration
@EnableSwagger2WebMvc
// 对JSR303提供支持
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfig {
@Bean
public Docket defaultApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("实战指南:使用Gateway、MybatisPlus和Nacos实现登录、JWT身份验证以及信息查询")
.select()
// 添加@Api注解才显示
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
// 这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.xju.edu"))
.paths(PathSelectors.any())
.build();
}

/**
* swagger-api接口描述信息
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("实战指南:使用Gateway、MybatisPlus和Nacos实现登录、JWT身份验证以及信息查询")
.description("实战指南:使用Gateway、MybatisPlus和Nacos实现登录、JWT身份验证以及信息查询")
.contact(
new Contact(
"yang",
"http://localhost:8888",
"2098116562@qq.com"
)
)
.version("1.0.0")
.build();
}
}

配置类创建完成后,创建factories文件:spring.factories文件是Spring Boot中非常重要的一个文件,它能够帮助应用程序自动配置和自定义配置,从而提高开发效率和代码质量。

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xju.edu.config.Knife4jConfig

3.3 创建demo-service模块

接下来创建demo-service模块,demo-service模块用于存放本次实战业务相关代码

这里使用MP的代码自动生成功能,这里就不赘述,可以通过浏览之前的博客 MybatisPlus扩展功能 | ycc personal blog

代码生成完成后对应目录下就会出现类

在demo-service模块下导入通用配置依赖:

1
2
3
4
5
<dependency>
<groupId>com.xju.edu</groupId>
<artifactId>demo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

然后在demo-service中导入Nacos依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>com.xju.edu</groupId>
<artifactId>demo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

在demo-service的resource目录下创建bootstrap.yaml文件.

为什么创建bootstrap.yaml文件?首先项目启动的时候,系统需要知道我们配置在nacos中的一些相关信息,例如mybatis等信息,但是我们的nacos配置在别的服务器上,系统读取不到nacos的配置文件,因此我们需要在本地创建一个bootstrap.yaml文件,用于存放一些关于nacos的信息.

bootstrap.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: demoservice
# 这里需要开启这个配置,不然后面的knife4j启动时会出现依赖冲突的问题
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cloud:
nacos:
# nacos address
server-addr: 192.168.200.131:8848
config:
file-extension: yaml
server:
port: 8888

这样项目就能访问到nacos中的配置信息了

接下来在nacos中创建demoservice.yaml文件,用于配置mybatis等相关信息

1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
# 这里改成自己的信息
password: 20212501006
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.xju.edu.pojo

这里博主在启动demo-service时报错,显示

1
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in class path resource 

这是由于SpringBoot版本,SpringCloud版本以及Nacos版本冲突的原因,请好好检查三个版本,后续我会把自己的版本贴出来

我的版本:

根项目的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</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-web</artifactId>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

这里标签被包在中,这样在子模块中就可以导入依赖而不用指定版本了

demo-service的pom.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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>gateway-token</artifactId>
<groupId>com.xju.edu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>demo-service</artifactId>

<dependencies>
<dependency>
<groupId>com.xju.edu</groupId>
<artifactId>demo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

demo-common模块依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>

这里博主使用的是2.3.9.RELEASE以及JDK8

配置好后创建启动类ServiceApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.xju.edu;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* Created by IntelliJ IDEA
*
* @author YangChengCheng
* 2023/12/18 11:23
**/
@SpringBootApplication
@MapperScan("com.xju.edu.mapper")
@EnableDiscoveryClient
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class,args);
}
}

启动微服务后查看Nacos的服务列表,发现已经被注册

然后项目启动后,访问localhost:8888/doc.html查看knife4j是否配置成功

image-20231218191658659

knife4j配置成功

4.编写接口并测试

首先我们在demo-service中编写接口,用于查询所有用户

4.1查询所有用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.xju.edu.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* Created by IntelliJ IDEA
*
* @author YangChengCheng
* 2023/12/18 20:21
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private Integer code;
private String msg;
private Object data;

public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

编写controller层查询所有用户代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* <p>
* 前端控制器
* </p>
*
* @author yang
* @since 2023-12-18
*/
@RestController
@RequestMapping("/user")
@Api(tags = "用户相关接口")
public class UserController {
@Autowired
private IUserService userService;

/**
* 获取所有用户
* @return
*/
@GetMapping
@ApiOperation("获取所有用户信息")
public Result getAllUsers(){
List<User> list = userService.list();
return new Result(200,"获取成功",list);
}
}

重启项目测试接口

image-20231218204700303

接口测试成功

但是我们要实现的功能是通过网关判断用户是否登录,用户登录了才能够获取相关信息,否则无法获取

因此我们接下来就要编写用户登录的相关接口

4.2 用户登录

4.2.1 导入工具类

首先在在utils软件包下面导入JwtUtils工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.xju.edu.utils;


import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

/**
* Created by IntelliJ IDEA
*
* @author YangChengCheng
* 2023/12/18 21:01
**/
public class JwtUtil {
// TOKEN的有效期一天(S)
private static final int TOKEN_TIME_OUT = 3_600;
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
// 最小刷新间隔(S)
private static final int REFRESH_TIME = 300;

// 产生token 根据签发时间,说明滚,接受用户,数据压缩方式等
public static String getToken(Long id){
Map<String, Object> claimMaps = new HashMap<>();
// 将id放在token的荷载中
claimMaps.put("id",id);
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
//签发时间
.setIssuedAt(new Date(currentTime))
//说明
.setSubject("system")
//签发者信息
.setIssuer("yang")
//接收用户
.setAudience("web")
//数据压缩方式
.compressWith(CompressionCodecs.GZIP)
//加密方式
.signWith(SignatureAlgorithm.HS512, generalKey())
//过期时间戳
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))
//将id添加到荷载中
.addClaims(claimMaps)
.compact();
}

/**
* 获取token中的claims信息
* 这里的claim是头部+荷载
* @param token
* @return
*/
private static Jws<Claims> getJws(String token) {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token);
}

/**
* 获取payload body信息
* 这里获取荷载信息
* @param token
* @return
*/
public static Claims getClaimsBody(String token) {
try {
return getJws(token).getBody();
}catch (ExpiredJwtException e){
return null;
}
}

/**
* 获取hearder body信息
* 这里获取头部信息
* @param token
* @return
*/
public static JwsHeader getHeaderBody(String token) {
return getJws(token).getHeader();
}

/**
* 是否过期
* 判断token是否还有效
* @param claims
* @return -1:有效,0:有效,1:过期,2:过期
*/
public static int verifyToken(Claims claims) {
if(claims==null){
return 1;
}
try {
claims.getExpiration()
.before(new Date());
// 需要自动刷新TOKEN
if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
return -1;
}else {
return 0;
}
} catch (ExpiredJwtException ex) {
return 1;
}catch (Exception e){
return 2;
}
}

/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}

JWT(JSON Web Token)是一种用于在网络应用间传递信息的安全方式,它由三个部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)需要注意的是,虽然 JWT 是经过 Base64 编码的,但它并不是加密的,因此不应将敏感信息直接存储在 JWT 中。签名只用于验证 JWT 的完整性,而不会隐藏实际数据。

4.2.2 编写业务层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
/**
* 用户登录
*
* @param username
* @param password
* @return
*/
@Override
public Result login(String username, String password) {
// 参数检验
if (username == null || password == null){
return new Result(400,"参数不能为空");
}
// 查询用户
User user = lambdaQuery().eq(User::getUsername, username).eq(User::getPassword, password).one();

// 校验查询结果
if (user == null){
// 未查询到结果 返回提示
return new Result(400,"用户密码或账号错误");
}
// 根据userId获取token
String token = JwtUtil.getToken(user.getUserId().longValue());

// 创建map用于返回数据
HashMap<String, Object> map = new HashMap<>();
// 存放token
map.put("token",token);
// 将获取到的对象也返回给前端
// 敏感信息置空
user.setPassword("");
user.setPhone("");
// 存入map
map.put("user",user);
return new Result(200,"登录成功",map);
}
}

这里使用了MP的lambdaQuery.

4.2.3 编写控制层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/user")
@Api(tags = "用户相关接口")
public class UserController {
@Autowired
private IUserService userService;

/**
* 获取所有用户
* @return
*/
@GetMapping
@ApiOperation("获取所有用户信息")
public Result getAllUsers(){
List<User> list = userService.list();
return new Result(200,"获取成功",list);
}

/**
* 用户登录接口
* @param username 用户名
* @param password 密码
* @return
*/
@PostMapping("/login")
@ApiOperation("用户登录接口")
public Result login(@RequestParam("username") String username, @RequestParam("password") String password){
return userService.login(username,password);
}
}

4.2.4 测试

登录成功

登录失败

测试成功

其实这里用户登录应该要对用户的密码进行盐值的加密,但是由于本篇博客只是大致复习一下网关验证的部分,因此在这里不做强调.

5.创建demo-gateway并进行配置

5.1 导入依赖以及创建配置文件

首先是创建demo-gateway模块并引入相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>

创建bootstrap.yml文件

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 51601
spring:
application:
name: demogateway
cloud:
nacos:
discovery:
server-addr: 192.168.200.132:8848
config:
server-addr: 192.168.200.132:8848
file-extension: yml

5.2 创建启动类

创建启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.xju.edu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* Created by IntelliJ IDEA
*
* @author YangChengCheng
* 2023/12/18 22:46
**/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}

5.3 创建过滤器

创建过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.xju.edu.filter;

import com.alibaba.spring.util.BeanUtils;
import com.xju.edu.util.JwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import org.apache.http.protocol.HTTP;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


/**
* Created by IntelliJ IDEA
*
* @author YangChengCheng
* 2023/12/18 23:05
**/
@Component
@Order
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 首先获取到请求的路径
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
System.out.println("请求的路径为:" + request.getURI().getPath());
// 如果请求的路径有/login,则放行,让其进行登录操作
if (request.getURI().getPath().contains("/login")){
// 放行
return chain.filter(exchange);
}
// 获取token
String token = request.getHeaders().getFirst("token");
// 判断token是否存在
if (token == null){
// 没有token 认真失败 返回
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// token存在 判断token是否过期
Claims claimsBody = JwtUtil.getClaimsBody(token);
int verifyToken = JwtUtil.verifyToken(claimsBody);
if (verifyToken == 1 || verifyToken == 2){
// token过期了
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// token存在且没有过期
return chain.filter(exchange);
}
}

创建nacos配置文件demogateway.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
routes:
# 平台管理
- id: demoservice
uri: lb://demoservice
predicates:
- Path=/user/**
filters:
- StripPrefix= 1

5.4 思路详解

整个过程实现的思路是这样的,首先前端发送请求的路径为localhost:51601/user/user/list,由于路径开头为/user,所以会被网关拦截,首先会经过网关判断当前路径中是否含有/login,如果有/login,则放行,让用户登录,如果没有/login,则判断用户要执行其他操作,则判断用户是否携带token,如果没有携带token,则直接返回401状态,如果携带了token但是过期了依然返回401状态,如果没有过期,则网关负载均衡到路径/user/list,这里由于nacos配置中设置了filters:StripPrefix=1,因此会去除第一个路径/user,访问路径为/user/list,则获取到用户的相关信息.

5.5 测试

由于Knife4j的测试接口是固定的,不能很好的满足我们的测试需求,所以这里使用的是Postman.

首先测试不携带token访问/user/list方法

显示状态码为401,测试成功

接下来测试携带token的,首先使用登录功能获取token

获取token成功,然后我们在request的header中将设置token

获取成功,测试完成,本次实战结束

6.总结

本篇博客完成了使用Gateway、MybatisPlus和Nacos实现登录、JWT身份验证以及信息查询,但是由于本次目的是为了复习jwt身份验证以及部分微服务知识,因此在部分细节上处理的并不好。例如:

  1. 对用户密码进行加密处理,加盐处理的步骤为 :a.用户注册时,服务器生成一个随机的盐值,并将其保存到数据库中。同时,将用户输入的密码和盐值拼接在一起,并进行哈希计算,得到一个固定长度的加密后的密码值。b.服务器将盐值和加密后的密码值保存到数据库中,作为该用户的密码凭证。c.用户登录时,向服务器发送用户名和密码。服务器从数据库中读取该用户的盐值,并将其与用户输入的密码拼接在一起,再进行哈希计算,得到一个临时的加密密码值。d.服务器将临时的加密密码值与数据库中保存的密码值进行比对,如果两者相同,则表示用户输入的密码正确,登录成功。
  2. 在对一些参数或者从数据库查询出的数据的处理上应该使用工具类对参数进行合法性校验
  3. 在maven的依赖管理上依然有很大的冗余
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights ©本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处! yang
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信