使用jwt进行登录
一、遇见的错误汇总
1.数据库
(1)

解决:
net stop mysql 关闭数据库
mysqld --console --skip-grant-tables --shared-memory 重新打开一个窗口,设置免密登录数据库
重新 打开一个窗口输入,这个时候可以不用输入密码 mysql -u root
设置密码为空update user set authentication_string='' where user='root';
(2)

解决:未启动数据库 net start mysql
(3)启动springboot后数据库报错

解决:配置文件里应该是ip地址而不是连接名称

2.springboot
(1)

解决:springboot和mybatis版本不兼容 ,修改后的配置文件如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</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.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
(2)空指针异常/没反应/找不到
解决:记得加上注解


(3)apifox里的登录接口报错403
解决:创建接口时记得把请求方法的get改成post

(4)请求加密解密报错
解决:更换jjwt的依赖,不同的依赖加密解密的方法可能不同

(5)连接失败
解决:经过查看发现是没有配置redis的数据连接
二、jwt
1.概念及流程




2.要做的事情
- 登录 。要做(1)自定义登录接口 实现到provider的查询;成功则生成jwt,并存入redis(2)实现userdetailservice,实现到数据库的自定义查询

- 校验:定义jwt认证过滤器,获取token,解析token,用userid从redis中获取信息,存入contextholder中(让其他过滤器可以访问 )

- 关联:将userid存到redis,当用户发起请求时使用userid去redis里查询

3.代码编写
-
准备工作
(1)引入依赖 ,依赖见第一部分的springboot报错处
(2)添加redis的序列化器和配置类、以及其他工具类,代码见下面链接,直接复制就可以。添加的工具包如下
FastJson序列化、redis配置类、FastJsonRedisSerializer、RedisConfig、RedisCache、JwtUtils工具类、跨域配置、MP分页拦截器-CSDN博客
最后添加完的目录如下

(3)在application.yml文件里配置数据库的连接。创建user数据库,并在代码中创建user实体类,上面记得加上@tablename注解
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mydemo?useUnicode=true&
characterEncoding=UTF-8&
useJDBCCompliantTimezoneShift=true&
useLegacyDatetimeCode=false&
serverTimezone=Asia/Shanghai&
allowMultiQueries=true&
useAffectedRows=true
username: root
password:
(4)创建mapper包,并在application上面加上包扫描

-
实现数据库校验核心用户
(1)编写userdetailservice的实现类,重写方法:userdetailservice接口会接受用户登陆时填写的用户名和密码,我们只要继承这个接口实现对数据库的查询。原本的代码里userdetailservice会查询后封装成detail返回,现在我们继承service后要返回继承了detail的类。

(2)查询到有结果的时就封装成userdetails返回(创建loginuser来实现),没结果就抛出异常(异常处理已经在前面的类里实现)


(3)loginuser记得把下面默认的false改成true。注意,需要往用户表中写入用户数据,并且如果想让用户的密码是明文存储,需要在密码前加{noop}。例如:{noop}1234

(4)将密码改成密文传输:默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password ,它会根据id去判断密码的加密方式。
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码验。
方式:定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。这里使用@EnableWebSecurity注解开启Spring Security的功能 代替了 implements WebSecurityConfigurerAdapter
ps:PasswordEncoder是BCryptPasswordEncoder的父类


-
实现登录接口 :生成jwt+存入redis
思路:自定义一个登录接口,在不需要登陆的时候也可以访问。
=>通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。原本用authenticate认证是使用authentiction类型认证,因此这里的user也应该封装成authentiction,这里使用
UsernamePasswordAuthenticationToken封装。AuthenticationManager最后会调用前面编写的userdetailservice去数据库进行认证。
=>认证成功的话要生成一个jwt,放入响应中返回;并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key
(1)SecurityConfig配置放行接口

(2)在impl类里面请求。此时认证返回的authenticate的principal封装了之前自己写的loginuser,因此要通过get获取;最后将user存入redis并返回给前端token



-
实现过滤器|| 获取token=》解析token=》从redis中获取用户信息=》存入securitycontexthoder(后面的请求都是从此地方获取信息)
(1)使用OncePerRequestFilter作为过滤器,因为使用默认的过滤器可能会被调用多次,此过滤器只会调用一次。能查得到则放行

(2)配置过滤器到前面,以便接受请求,在securityconfig类里面配置

(3)编写退出登录接口:获取SecurityContextHolder中的认证信息,删除redis中对应的数据,并在impl和service里做相应修改


(4)自定义失败处理。返回json,这样可以让前端能对响应进行统一的处理。
在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置SpringSecurity即可。


浙公网安备 33010602011771号