【Springboot】Vue3-Springboot引入JWT实现登录校验以及常见的错误解决方案

文章目录

  • 前言
  • 一、JWT简单介绍
  • 二、token校验设计思路
  • 三、使用步骤
    • Springboot部署JWT
    • 引入依赖:
    • 创建登录实体类
    • 后端:LoginController.java
    • 路由守卫函数
  • 四、问题

前言

项目版本:
后端: Springboot 2.7、 Mybatis-plus、Maven 3.8.1
数据库:MySQL 8.0
前端:Vue3、Axois 1.6.0 、Vite 4.5.0、Element-Plus、Router-v4

一、JWT简单介绍

JWT 全称 JSON Web Token,是一种基于 JSON 的数据对象,通过技术手段将数据对象签名为一个可以被验证和信任的令牌(Token)在客户端和服务端之间进行安全的传输。

二、token校验设计思路

1. 首先,用户从登录请求发往后端后,后端生成token,并将token返回给前端。
2. 前端拿到后端生成的token后,保存在localStorage中,在token时效内,用户拿着这个token访问系统所有的功能。
3. 一旦token失效,系统将会强制用户退出系统,直到重新登录才能获取新的token.如此循环。

三、使用步骤

Springboot部署JWT

在整个JWT token的周期中,只需要在用户登录的时候生成token,其余访问页面均用vue3的路由守卫拦截,用户向后端发起的请求中都会携带token,后端只有token校验合法后才会执行具体的业务。

引入依赖:

    <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

创建登录实体类

package com.fy36.hotelmanage.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;

@Data
@ToString
@TableName(value = "tbadmin")
public class Admin {
    private String username;
    private String password;
    @TableId(type = IdType.AUTO)
    private Long Id;
    @TableField(exist = false) //token字段不映射到数据库,只是用来携带。
    private String token;

}

上面的@TableFiled exist=false,表示不对照对应的sql字段,因为token校验并不存到数据库中,只是用来存储到用户实体中,发往前端校验。如果不添加该字段,将会报错"Unkown column if filed list in ‘tbadmin.token’.如下是admin表中的字段设计。

在这里插入图片描述

  1. 创建JwtUtils.java
public class JWTUtils {
    private static long TIME = 1000 * 5; //token有效期,以毫秒为单位,所以这里token有效期为5s.
    private static String SIGNATURE = "2786"; //私钥,签名
    public static String createToken(Admin admin) {
        JwtBuilder jwtBuilder = Jwts.builder(); //构建jwt对象
        //配置header
        String jwtToken = jwtBuilder
                //配置hader
                .setHeaderParam("alg", "HS256") //签名算法
                .setHeaderParam("typ", "JWT")   //TYPE 为JWT
                //payload,载荷,不要加入隐私信息
                .claim("username", admin.getUsername()).setExpiration(new Date(System.currentTimeMillis() + TIME)) //假定token有效时间为24x小时
                //signature
                .signWith(SignatureAlgorithm.HS256, SIGNATURE)
                //拼接该三部分,构成一个完整的token
                .compact();
        return jwtToken;
    }

    public static boolean checkToken(String token) {
        if (token == null) {
            return false;
        }
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SIGNATURE).parseClaimsJws(token);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

如下是前端点击登录按钮后,触发的登录方法:

//测试请求方法
const login = function () {
  //测试样例2
  api
    .post("/login", {
      ...formLabelAlign,
    })
    .then(function (res) {
      if (res.data.code == 200) {
        ElMessage.success("登录成功!");
        //用户登录成功后,将后端生成的token,放到localStorage中。
        //如果收到了后端发送过来token,那么存储token,并跳转到系统界面。
        if (res.data.data.token) {
          console.log("输出res");
          console.log(res.data);
          localStorage.setItem(
            "token_access",
            JSON.parse(JSON.stringify(res.data.data.token))
          );
        }
        //存储好token后,进入系统。
        router.push("/home");
      } else {
        ElMessage.error("用户名或密码错误,请重新输入");
      }
    });
};

用户点击后,向后端发送请求,对应/login接口

后端:LoginController.java

当用户名和密码都正确后,使用JwtUtils.class 生成token,并将它存放到Admin实体类中的token 字段中,发往前端。

    @PostMapping("/login")
    public ApiResult login(@RequestBody Admin admin) {

        Admin adminRes = loginService.adminLogin(admin);
        if (adminRes != null) {
            //设置token,发往前端口
            adminRes.setToken(JWTUtils.createToken(adminRes));
            System.out.println("后端生成的token为:\n" + adminRes.getToken());
            return ApiResultHandler.buildApiResult(200, "请求成功", adminRes);
        } else return ApiResultHandler.buildApiResult(400, "请求失败", "用户名账号或密码错误");

/**
校验token
**/
    @GetMapping("/checkToken")
    public boolean checkToken(HttpServletRequest request) {
        System.out.println("reqeust:---------");
        System.out.println(request.toString());
        String token = request.getHeader("token");
        System.out.println("本地拿到的前端token为:");
        System.out.println(token);
        token = token.replaceAll("\"", "");
        System.out.println("处理后的token为:");
        System.out.println(token);
        boolean res = JWTUtils.checkToken(token);
        System.out.println("校验结果" + res);
        return res;
    }

OK,此时前端收到了token,会将它存放在localStorage中。
在这里插入图片描述

对应前端的login片段为:

  localStorage.setItem(
            "token_access",
            JSON.parse(JSON.stringify(res.data.data.token))
          );

JSON.stringify()方法可以用于将JavaScript对象转换为字符串以便在网络上进行传输或存储。它还可以用于将JavaScript对象转换为字符串以便进行数据的序列化和持久化存储。

如图,在谷歌浏览器,F12打开控制台–Application中,可以查看存放的token.
(token不带引号)

在这里插入图片描述
此时用户成功进入系统,可以携带有效时期的token进行访问系统功能,但是用户每次点击系统其他功能时,将会校验token是否合法,主要检测的是token时效,如果超过这个时效,将会强制退出。那么,怎么让系统在每次用户请求时,都能自动发送给后端检验token呢?

这里用到的是router路由守卫函数router.beforeEach((to, from, next)

路由守卫函数,写在了main.js中.当用户每次调用后端服务时,都会携带已保存的token,发往后端,这里将token,放入了请求头中,后端使用HttpServletRequest 来获取请求头.(代码看上面的LoginController.class)

路由守卫函数

路由守卫函数:

//进行任何跳转前,都需要进行该方法的调用。
... 
const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((to, from, next) => {
  if (to.path == "/login") {
    // 登录或者注册才可以往下进行
    window.localStorage.removeItem("token_access");//移除token
    next();
  } else {
    // 获取 token
    let admin_token = JSON.stringify(
      window.localStorage.getItem("token_access")
    );
    // token 不存在
    if (admin_token === null || admin_token === "") {
      ElMessage.error("您还没有登录,请先登录");
      next("/login");
    } else {
      //校验token合法性
      api.get("/checkToken", {
          headers: {
            token: admin_token,
          },
        })
        .then(function (res) {
          if (res.data) {
            //token校验发现合法
            console.log("token合法");
            // router.push("/home");
          } else {
            ElMessage.error("token校验不合法,请重新登录");
            localStorage.removeItem("token_access");
            router.push("/login");
          }
        })
        .catch(function (error) {
          ElMessage.error("token已失效,重新登陆!");
          console.log(error);
        });
      next();
    }
  }
});

有一点需要注意的是,token在前后端交互的过程中,格式的变化,如下图是控制台输出的token交互中的变化。token放到header的方法博客

本地拿到的前端token为:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjExMSIsImV4cCI6MTY5OTQxOTAxMn0.Y7mbTlsp5dJ1-hKE8RCtviZwFIC3E_CdjhFPDsMT5Ws"

处理后的token为:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjExMSIsImV4cCI6MTY5OTQxOTAxMn0.Y7mbTlsp5dJ1-hKE8RCtviZwFIC3E_CdjhFPDsMT5Ws

在token校验中,往往不是token时效导致校验失败,而因为前后端交互的token的格式不一致导致校验失败。前端传入的token需要在后端去除两边的双引号。

StackOverflow的解决方案: Storing my API token in local storage is wrapping the token in double quotes

四、问题

问题1:io.jsonwebtoken.UnsupportedJwtException: Signed Claims JWSs are not supported
问题就是:不支持已签名的声明JWS。
如果使用 Jwts.builder() 创建token,在解析时,就需要使用 parseClaimsJws(token) 而不是 parseClaimsJwt(token) .

问题2:JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
中文意思是:JWT签名与本地计算的签名不匹配。无法断言JWT有效性,不应信任JWT有效性
出现这种异常的情况正如上面所说,token是一串字符串且不带双引号,后端需要进行 token = token.replaceAll("\"", "");处理。

问题3:Cannot access ‘res’ before initialization
请根据提示,查看这个result变量,是否在 代码下文 中是否重新进行了let res 重新定义之类的操作。

问题4:使用localStorage.getItem方法获取token时,发现token的形式外围包围了一层引号

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

这是因为,没有使用JSON.parse,将该字符串转化为javascript对象。

改写为:window.localStorage.getItem(JSON.stringify(token));

问题5:JWT expired at 2023-11-07T15:42:27Z. Current time: 2023-11-07T15:42:27Z, a difference of 105 milliseconds. Allowed clock skew: 0 milliseconds.
这说明token 已经过期了,具体是在JWTUtils.java中的:

 String jwtToken = jwtBuilder
                //配置hader
                .setHeaderParam("alg", "HS256") //加密算法
                .setHeaderParam("typ", "JWT")   //TYPE 为JWT
                //payload,载荷,不要加入隐私信息
                .claim("username", admin.getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + TIME)) //假定token有效时间为24x小时
                //signature
                .signWith(SignatureAlgorithm.HS256, SIGNATURE)
                //拼接该三部分,构成一个完整的token
                .compact();

中的setExpiration ,这里new Date是毫秒级别的当前时间,该语句含义就是,在当前时间之后的TIME毫秒,是有效的。之前这里改了之后还是token过期,尝试先把TIME改的更大,然后重新运行一下Springboot。另外,还有一个token时间不生效的原因是,当前已经登录的用户token时效已经设定,此时需要执行· localStorage.removeItem("token_access") 来删除掉原有的token,重新登陆一次,新token就会根据当前时间来修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/124504.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络测试工具—— iperf2 安卓APK 下载 及简单使用

网络测试工具—— iperf2 安卓APK 下载 及简单使用 前言一、iperf2是什么&#xff1f;二、使用步骤附上help中命令截图翻译总结 前言 项目上有一款安卓车机加载局域网图片加载非常慢&#xff0c;所以需要测试一个安卓车机设备的带宽&#xff0c;经过调研后使用到了iperf2。 一…

项目管理之如何出道(中)

昨日立冬&#xff0c;各位盆友&#xff0c;有没有吃饺子&#xff1f; 冬天来了&#xff0c;寒冷未约而至&#xff0c;冬雪侵袭北国。未知的变化总能让人产生恐慌和无措&#xff0c;就像行走在荒岛小路&#xff0c;前面遇到的究竟是迷人之景&#xff1f;还是饿狼之瞟&#xff1f…

52基于MATLAB的希尔伯特Hilbert变换求包络谱

基于MATLAB的希尔伯特Hilbert变换求包络谱&#xff0c;对原始信号进行初步滤波&#xff0c;之后进行包络谱分析。可替换自己的数据进行优化。程序已调通&#xff0c;可直接运行。 52的尔伯特Hilbert变换包络谱 (xiaohongshu.com)

计算机三级四级嵌入式备战经验

2023年9月23日于东北大学考完三四级 大四的时候时间比较多&#xff0c;因为本科学了一点嵌入式的知识&#xff0c;研究生又用不到&#xff0c;所以想着考个证金盆洗手。 三级考的是一本书&#xff0c;更多涉及到S3C2410这个芯片&#xff1b;四级考的是两本书&#xff1a;《操作…

故障注入测试目的及方法

在软件开发的复杂环境中&#xff0c;保证应用程序的鲁棒性和稳定性是至关重要的。故障注入测试是一种专门设计用于模拟和评估系统对故障的响应能力的测试方法。通过主动引入故障并观察系统的行为&#xff0c;开发者可以更全面地了解系统在面临异常情况时的表现。 一、故障注入测…

人工智能入门:什么是“具身智能“?

具身智能&#xff08;Embodied Intelligence&#xff09;是一种智能系统的设计理念&#xff0c;其目标是通过将感知、决策和行动融合在一起&#xff0c;使机器能够像人类一样具备身体和运动能力。具身智能的核心理念是利用机器的身体结构和动作能力来增强其智能表现和解决复杂任…

git解决冲突的方法。

1、 cherry-pick git fetch ssh://jingyou.caigerrit.transtekcorp.com:29418/leshan refs/changes/23/34123/3 && git cherry-pick FETCH_HEAD2、 文件解冲突&#xff01; 3、 cherry-pick完整。 git cherry-pick --continue4、查看状态。 5、 push。 git push o…

混合云中 DevOps 的最佳实践

近年来&#xff0c;出现了各种工具、技术和框架&#xff0c;其目标是增强灵活性、性能和可扩展性。传统的整体方法已被微服务和纳米服务等更加模块化的方法所取代。此外&#xff0c;云计算的兴起导致本地软件被云环境所取代&#xff0c;云环境提供了以前无法提供的广泛优势和功…

Ubuntu中安装rabbitMQ

一、安装 RabbitMQ ①&#xff1a;更新源 sudo apt-get update②&#xff1a;安装Rrlang语言 由于RabbitMq需要erlang语言的支持&#xff0c;在安装RabbitMq之前需要安装erlang sudo apt-get install erlang-nox③&#xff1a;安装rabbitMQ sudo apt-get install rabbitmq-s…

彻底改变您的用户体验设计:您需要了解的 5 个工具包和指南

问题 进行设计冲刺、设计思维工作坊期间&#xff0c;如何找到好用的UX工具&#xff1f; 市面上有很多优秀的UX书籍&#xff0c;但也有越来越多的在线 用户体验设计 工具包和方法指南详细介绍了大量的UX工具和方法&#xff0c;包括这些方法是什么、为什么要用、何时用还有怎么…

redis主从复制玩法全过程笔记(redis7+版本)

目录标题 环境目的实操一主多仆服务器和本地主机配置环境docker 环境配置 薪火相传反客为主 主从复制的流程主从复制的特性主从复制的缺点本篇结语 环境 我的环境介绍window环境VM虚拟机一台并安装centos7&#xff0c;一台阿里云Linux服务器&#xff0c;另一台Linux系统主机并…

第四章:人工智能深度学习教程-激活函数(第三节-Pytorch 中的激活函数)

在本文中&#xff0c;我们将了解 PyTorch 激活函数。 目录 什么是激活函数以及为什么使用它们&#xff1f; Pytorch 激活函数的类型 ReLU 激活函数&#xff1a; Python3 Leaky ReLU 激活函数&#xff1a; Python3 S 形激活函数&#xff1a; Python3 Tanh 激活函数&am…

死锁问题概述

文章目录 死锁的概念死锁的定义相似概念&#xff1a;饥饿死锁产生的原因死锁产生的必要条件死锁的预防破坏互斥条件破坏不可剥夺/不可抢占条件破坏请求并保持条件破坏循环等待条件 死锁避免安全性算法 死锁的处理策略死锁的检测死锁的解除 死锁的概念 死锁的定义 多个进程由于…

使用超融合,网络交换机如何选型与配置?

很多用户在部署超融合集群时&#xff0c;都会关注网络交换机的选型与配置。我们在这篇文章中整理了一些关于网络交换机的常见提问&#xff0c;并邀请 SmartX 技术专家进行了详细解答。 Q1. 超融合架构下&#xff0c;网络交换机是如何部署的&#xff1f;需要多少台交换机&#x…

火爆全网!用 Pyecharts 就能做出来“迁徙图“和“轮播图“

1.pyecharts知识点回顾 1&#xff09;知识回顾 前面我们已经讲述了&#xff0c;如何使用pyecharts进行图形的绘制&#xff0c;一共涉及到如下四步。我们今天就是按照下面这几步来进行迁徙图和轮播图的绘制。 ① 选择图表类型&#xff1b; ② 声明图形类并添加数据&#xff1…

Linux-Shell命令行解释器的模拟实现

引言&#xff1a;本篇文章主要是简单实现一个shell命令行解释器&#xff0c;可以支持基础常见的linux的命令&#xff0c;支持内建命名echo、cd&#xff0c;同时支持重定向的操作&#xff01; 一、代码剖析 1. 头文件引入&#xff1a; 因代码是在linux下实现&#xff0c;引入的…

全国5米高程DEM数据及衍生的全国地形起伏度数据

地表起伏度&#xff0c;也有称为地势起伏度、地形起伏度&#xff0c;是指某点在其确定面积的域内的最高点与最低点之间的高差。地表起伏度概念的核心在于如何确定该点的计算域。在统计意义上&#xff0c;随着计算域范围的增大&#xff0c;地表起伏度将逐渐增大。 因此&#xff…

GitHub上的开源工业软件

github上看到一个中国人做的流体力学开源介绍&#xff0c;太牛了&#xff01; https://github.com/clatterrr/FluidSimulationTutorialsUnity 先分析一下工业仿真软件赛道 工业仿真软件的赛道和产品主要功能如下&#xff1a; 1. 工艺仿真赛道&#xff1a; - 工厂布局优化&am…

linux 下 mysql8 修改root初始密码

背景 linux下安装完mysql8以后&#xff0c;无法使用root用户登录或者忘记了root用户的密码&#xff0c;需要修改root用户的密码&#xff1b; 步骤 不想翻译了&#xff0c;专业人员都能看懂。完结 链接 MySQL :: MySQL 8.0 Reference Manual :: B.3.3.2 How to Reset the Roo…

MATLAB|不给糖果就捣蛋

目录 扫一扫关注公众号 效果图 代码 绘制南瓜 绘制无脸男小鬼 其中绘制风车代码&#xff1a; 其中 EllipsePlotter类函数代码如下 属性 (properties) 方法 (methods) 扫一扫关注公众号 效果图 代码 绘制南瓜 clc;clear;close all; [X,Y,Z]sphere(200); R1(-(1-mod(0:…