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

news2025/1/11 10:55:12

文章目录

  • 前言
  • 一、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就会根据当前时间来修改。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序发成绩

在这个数字化快速发展的时代&#xff0c;让学生能够方便快捷地获取自己的成绩已经成为一项基本的需求。那么&#xff0c;如何实现这一目标呢&#xff1f;对于许多老师来说&#xff0c;可能首先想到的是使用各种代码或者Excel来发布成绩查询。今天&#xff0c;我们就来探讨一下这…

【第2章 Node.js基础】2.3 Node.js事件机制

2.3 Node.js事件机制 学习目标 &#xff08;1&#xff09;理解Node.js的事件机制&#xff1b; &#xff08;2&#xff09;掌握事件的监听与触发的用法。 文章目录 2.3 Node.js事件机制什么是事件机制为什么要有事件机制事件循环事件的监听与触发EventEmitter类常用API 什么是…

深度学习 opencv python 实现中国交通标志识别 计算机竞赛

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

使用python批量修改图片名称

一、使用场景 修改模式&#xff1a;原图片名称.png 》 目标图片名称.png条件&#xff1a;目标图片名称 包含 原图片名称准备工作&#xff1a;目标图片名称填写在excel当中&#xff0c;把excel放进图片文件夹内 二、代码示例 import os import pandas as pd import numpy as …

西周就已经开始用WIFI了?带你了解路由器的“前世今生”

说起路由器&#xff0c;如果再往前10年&#xff0c;可能还不是那么普及&#xff0c;但如今再提及&#xff0c;基本上已经是家家户户必备的科技产品之一&#xff0c;无论是老人还是小孩对它也都很熟悉了&#xff0c;这样的变化不禁让人感叹科技的发展之快。 去年的三星堆文物又有…

2011年03月17日 Go生态洞察:探索Go与C的交互——Cgo

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

使用eXplorer本地搭建免费在线文件管理器并实现远程登录——“cpolar内网穿透”

文章目录 1. 前言2. eXtplorer网站搭建2.1 eXtplorer下载和安装2.2 eXtplorer网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1. 前言 通过互联网传输文件&#xff0c;是互联网最重要的应用之一&#xff0c;无论是…

【算法-链表3】删除链表倒数第n个节点 + 链表相交

今天&#xff0c;带来XXX的讲解。文中不足错漏之处望请斧正&#xff01; 理论基础点这里 删除链表倒数第n个节点 1. 思路 直接计算 倒数第n个 正数第size-n1个 比如要删除size为5的链表的倒数第2个节点。 其实就是正数size-n1个。那么&#xff0c;从第一个走到底size-n1个…

【uniapp uview】u--textarea组件custom validator check failed for prop “confirmType“

加一行&#xff1a;confirmType"done"即可。<UTextarea> at node_modules/uview-ui/components/u-textarea/u-textarea.vue<UTextarea> at node_modules/uview-ui/components/u--textarea/u--textarea.vueat pages/notice/noticeDetail.vueuview 文档地址…

73.矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&#xff1a; 输入&…

每天一点python——day65

#每天一点Python——65 #字符串的内容对齐操作类似于word中左对齐、右对齐、居中对齐如图 #例&#xff1a; s1hello,python print(s1.center(20,*))#设置宽度20&#xff0c;填充图是*s1有12个字符&#xff0c;这个字符串的宽度设置为20&#xff0c; 20-128 因为center是居中对齐…

概念解析 | 雷达协同认知成像:原理、研究现状与挑战

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:雷达协同认知成像。 概念解析 | 雷达协同认知成像:原理、研究现状与挑战 摘要: 雷达协同认知成像,作为一种先进的感知技术,在军事侦察、民用航空、无人驾驶等领域具有广…

GZ038 物联网应用开发赛题第2套

2023年全国职业院校技能大赛 高职组 物联网应用开发 任 务 书 (第2套卷) 工位号:______________ 第一部分 竞赛须知 一、竞赛要求 1、正确使用工具,操作安全规范; 2、竞赛过程中如有异议,可向现场考评人员反映,不得扰乱赛场秩序; 3、遵守赛场纪律,尊重考评人员,…

NFS文件系统共享服务器实战

架设一台NFS服务器&#xff0c;并按照以下要求配置 准备 两台Linux虚拟机一台作为服务端server&#xff0c;一台作为客户端client server IPV4&#xff1a;192.168.110.136/24 client IPV4&#xff1a;192.168.110.134/24 两台服务器都需要关闭防火墙和seLinux 服…

基于安卓android微信小程序的物流仓储系统

项目介绍 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个开发过程首先对物流仓储系统进行需求分析&#xff0c;得出物流仓储系统主要功能。接着对物流仓储系统进行总体设计和详细…

微信支付(JSAPI支付)接入流程

一、 常用支付方式 微信当前支持的支付产品有如下这些: 可以根据自己的需要选择接入的支付方式。 接入指引 我们常用的支付方式:JSAPI支付 Native支付 APP支付 JSAPI支付: 商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款Native支付: 商家在系统中按微信支…

Tomcat隐藏版本号和关闭默认管理页面

一. 隐藏Tomcat异常页面中的版本信息&#xff0c;Tomcat服务器版本号泄露 Tomcat/8.5.xx相关版本号等信息&#xff0c;是不安全的。这会被黑客获取到&#xff0c;利用该版本的其他漏洞对服务器进行异常操作&#xff0c;所以需要隐藏掉。 进入tomcat安装目录 apache-tomcat-8.…

MySQL系列-win10安装MySQL

MySQL系列-win10安装MySQL 1. MySQL系列-win10安装MySQL1.1MySQL下载安装MySQL5.71.2MySQL下载再安装MySQL8.0 未完待续 1. MySQL系列-win10安装MySQL 1.1MySQL下载安装MySQL5.7 下载地址 https://www.mysql.com/downloads/ 进入后&#xff0c;下拉页面&#xff0c;最下面有社…

Zabbix自定义监控内容案例(自动发现、自动注册)

一、自定义监控内容 案例&#xff1a;自定义监控客户端服务器登录的人数 需求&#xff1a;限制登录人数不超过3个人&#xff0c;超过5个人就发出报警 1、在客户端创建自定义key 创建Zabbix监控项配置文件&#xff0c;用于自定义Key #在zabbix的配置文件中定义了会加载放在/e…

Zabbix“专家坐诊”第210期问答汇总

问题一 Q&#xff1a;zabbix触发器条件基于历史趋势的函数有示例吗&#xff1f;还有这些基于历史趋势的函数&#xff0c;具体是读取历史表还是趋势表&#xff1f; A&#xff1a;读历史表&#xff0c;示例可以看看官网的。 https://www.bookstack.cn/read/zabbix-5.0-zh/37cf0…