Springboot集成JWT token实现权限验证

  • 紧接着上一次的博客,我们现在来给这个项目添加一个jwt的权限验证功能,上一次的博客如下:
  • springboot结合vue实现登录和注册功能-CSDN博客

1.后端 

1.1.导入依赖

        <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version></dependency>

1.2.编写jwt的拦截器

这个类实现一个HandlerInterceptor接口,这个类主要完成以下几个任务:

  • 从请求头里获取token,没有获取到就抛异常(注意:请求头里原本是没有token的,这个需要我们自己在前端添加一个token)
  • 解码token并从token里获取用户ID,没有获取到就抛异常,表明token里没有数据(注意:这个用户ID是自己在前端添加token时存储的)
  • 通过用户密码来生成一个验证器,解析token(JWT一般含有三个部分,头部,荷载,签名,解析过程中jwtVerifier会检验这三部分能不能正常分离,以及来用验证器来验证签名,以及检查token的过期时间)这一步也是最重要的一步!
package com.kuang.common;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.kuang.exception.ServiceException;
import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate UserMapper userMapper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//从请求头Header里接收传来的参数tokenString headerToken = request.getHeader("token");//如果传来的token为空,则从url参数中来接收传来的tokenif(StringUtils.isBlank(headerToken)){headerToken = request.getParameter("token");//如果url里的token为空,则抛异常}if (StringUtils.isBlank(headerToken)){throw new ServiceException("401","请登录");}//从token中获取userId//JWT.decode(headerToken) 解码JTW TokenString userId;try {userId = JWT.decode(headerToken).getAudience().get(0);} catch (JWTDecodeException e) {throw new ServiceException("401","请登录");}//根据userId查询数据库//userId是String类型,这里要转换成int类型User user = userMapper.selectUserById(Integer.parseInt(userId));//user为空,则抛异常if (user == null){throw new ServiceException("401","请登录");}//通过用户密码加密之后生成一个验证器JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {//验证tokenjwtVerifier.verify(headerToken);} catch (JWTVerificationException e) {throw new ServiceException("401","请登录");}return true;}
}

1.3.编写token的工具类

在这个类中有以下几点任务:

  • 生成token,并且将用户ID放在token的荷载(Payload)中当作受众声明(Audience),以及设置token的过期时间,把用户密码当作密钥,然后给token加一个签名,只有添加了签名,这个token才能被使用,而我们设置的这个密钥就是来验证签名的钥匙
package com.kuang.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;@Component
public class TokenUtils {private static UserMapper staticUserMapper;@AutowiredUserMapper userMapper;@PostConstructpublic void setUserService(){staticUserMapper = userMapper;}/*** 创建token* @param userId* @param sign* @return*/public static String createToken(String userId,String sign){// 获取当前时间Date currentDate = new Date();// 创建Calendar实例Calendar calendar = Calendar.getInstance();// 设置Calendar的时间为currentDatecalendar.setTime(currentDate);// 向前偏移两个小时calendar.add(Calendar.HOUR_OF_DAY, 2);// 获取偏移后的时间Date offsetDate = calendar.getTime();return JWT.create().withAudience(userId)//将userId保存到token里.withExpiresAt(offsetDate)      //2小时候token过期.sign(Algorithm.HMAC256(sign)); //将password作为token密钥}/*** 获取当前登录的用户信息* @return*/public static User getCurrentUser(){//获取当前请求的HttpServletRequest对象,这样就能在下面访问请求头、参数等HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();try {//从请求头里获取tokenString token = request.getHeader("token");//如果token不为空,则从Audience中获取第一个数据(是用户的id)if (StringUtils.isNotBlank(token)){String userId = JWT.decode(token).getAudience().get(0);return staticUserMapper.selectUserById(Integer.parseInt(userId));}} catch (Exception e) {return null;}return null;}}

 1.4.扩展springmvc的拦截器

这个类继承WebMvcConfigurationSupport类,主要任务有以下几点:

  • 将在第二步编写的jwt的拦截器注入到spring容器中,并将其添加到spring的拦截器中
  • 设置拦截路径

当用户访问下面设置好的拦截路径时,就会触发我们自己编写的jwt的拦截器,然后进入校验过程(就是第二步中的那一套流程)

package com.kuang.common;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {//配置jwt的拦截器规则,拦截所以请求,除了/user/login,/user/register,/file/uploadregistry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/file/upload");super.addInterceptors(registry);}@Beanpublic JwtInterceptor jwtInterceptor(){return new JwtInterceptor();}
}

 1.5.将后端生成的token返回给前端

一般会在用户登录时,将token返回给前端,下面这个类就是实现登录功能的service层的实现类

service层: 

 @Overridepublic User selectUserByUsername(User user) {User user1 = userMapper.selectUserByUsername(user);//生成token,userId用来放在token里,password用来生成token的验证器,来验证tokenString token = TokenUtils.createToken(String.valueOf(user1.getId()), user1.getPassword());user1.setToken(token);return user1;}

controller层: 

    //登陆功能@PostMapping("/login")public Result login(@RequestBody User user, HttpServletRequest request){User user1 = userService.selectUserByUsername(user);if (!user.getPassword().equals(user1.getPassword())){return Result.error("用户名或密码不正确");}//将id存入session//request.getSession().setAttribute("userId",user1.getId());//将含有token的user对象返回给前端return Result.success(user1);}

2.前端

2.1.导入request.js文件 

这里添加一个通用的request.js文件,这个文件用于以下几点:

  • 可以在请求发送前对请求做一些处理
  • 可以在接口响应后统一处理结果
  • 对请求路径前面的http:localhost做了封装,以便不用每次都写上
  • 对返回的数据做了封装,原本要访问后端返回的数据要这样写res.data.data,封装了之后可以简化为res.data

import axios from 'axios'
import router from "@/router";const request = axios.create({baseURL: 'http://localhost:8082',  // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!timeout: 5000
})// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {config.headers['Content-Type'] = 'application/json;charset=utf-8';//在请求头里添加一个tokenlet user = JSON.parse(localStorage.getItem("user") || '{}')config.headers['token'] = user.token;  // 设置请求头return config
}, error => {return Promise.reject(error)
});// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(response => {let res = response.data;// 如果是返回的文件if (response.config.responseType === 'blob') {return res}// 兼容服务端返回的字符串数据if (typeof res === 'string') {res = res ? JSON.parse(res) : res}if (res.code === '401'){router.push('/login')}return res;},error => {console.log('err' + error) // for debugreturn Promise.reject(error)}
)export default request

在上面的文件里,我们着重看这几行代码,在前端发送请求时,在请求头里添加一个token,并且从localStorage中获取在登录时存储的用户信息,用来存储在token中,具体程序如下:

在接口响应后,统一处理结果中的如下程序,如果用户没有token,后端就会返回401的错误,在这里就会处理401错误,进行页面跳转

导入这个文件之后,我们可以在main.js文件中注册这个文件的全局对象

2.2.采用request.js文件里提供的请求方式

 这里只提供一个例子,其他的地方都是一样的

3.流程图分析

下面的流程图涉及了后端以下几个类:

  • JwtInterceptor:1.2中的jwt的拦截器
  • TokenUtils:1.3中的token的工具类
  • InterceptorConfig:1.4中的springmvc的扩展类
  • LoginController:处理用户登录的controller方法,这个方法用来返回给前端token
  • selectUserController:在登录之后,处理前端发过来的查询用户的请求

解释上面的过程,用户在登陆之前还没有token,登录之后,通过调用TokenUtils来生成token,并且返回给前端,至此,该用户就有了token,在之后的请求中首先会被Interceptor-Config类拦截下来,然后进入JwtInterceptor类进行token的校验,成功后才会进入controller层,否则就会抛出401的异常(这里的异常是自己手动设置的),然后就会返回给前端,前端就会发生页面跳转,跳转到login页面

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

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

相关文章

2021年全国大学生电子设计竞赛D题——基于互联网的摄像测量系统(一)

01 D题实现效果演示 视频参考微信原文&#xff1a;2021年全国大学生电子设计竞赛D题——基于互联网的摄像测量系统&#xff08;一&#xff09; 02 D题任务要求 &#xff08;D题原文件参见本文附录&#xff09; 设计并制作一个图中所示的基于互联网的摄像测量系统。图中边长…

【Python】异常处理结构

文章目录 1.python异常2.try_except异常处理结构3.try... 多个except异常处理4.try_except_else异常处理结构5.try_except_finally异常处理结构6.常见报错类型 在运行代码时&#xff0c;总是遇到各种异常&#xff0c;且出现异常时&#xff0c;脚本就会自动的的停止运行&#xf…

就业班 第三阶段(nginx) 2401--4.17 day1 nginx1

负载均衡集群 1、集群是什么&#xff1f; 1 集群&#xff08;cluster&#xff09;技术是一种较新的技术&#xff0c;通过集群技术&#xff0c;可以在付出较低成本的情况下获得在性能、可靠性、灵活性方面的相对较高的收益&#xff0c;其任务调度则是集群系统中的核心技术。 …

FinalShell 远程连接 Linux(Ubuntu)系统

Linux 系列教程&#xff1a; VMware 安装配置 Ubuntu&#xff08;最新版、超详细&#xff09;FinalShell 远程连接 Linux&#xff08;Ubuntu&#xff09;系统Ubuntu 系统安装 VS Code 并配置 C 环境 ➡️➡️➡️提出一个问题&#xff1a;为什么使用 FinalShell 连接&#xff0…

初识ansible服务及ansible主机清单配置

目录 1、什么是自动化批量管理 2、自动化工具ansible架构 3、ansible服务专用术语对照表 4、设置主机清单&#xff08;inventory&#xff09; 4.1实验环境准备 4.2配置主机清单 4.2.1分组基本格式 4.2.2指定用户名&#xff0c;密码。端口 4.2.3子组 4.3查看 4.3.1看…

LeetCode———144—— 二叉树的前序遍历

目录 ​编辑 1.题目 2.解答 1.首先计算二叉树的节点个数&#xff1a; 2.以先序遍历&#xff08;Preorder Traversal&#xff09;的方式遍历一个二叉树&#xff0c;并将遍历到的节点的值存储在一个整数数组中 3.最终代码 1.题目 . - 力扣&#xff08;LeetCode&#xff09; 给…

双碳目标下基于“遥感+”集成技术的碳储量、碳排放、碳循环、温室气体等多领域监测与模拟

以全球变暖为主要特征的气候变化已成为全球性环境问题&#xff0c;对全球可持续发展带来严峻挑战。2015年多国在《巴黎协定》上明确提出缔约方应尽快实现碳达峰和碳中和目标。2019年第49届 IPCC全会明确增加了基于卫星遥感的排放清单校验方法。随着碳中和目标以及全球碳盘点的现…

[stm32]DMA使用

自动重装和M2M(软件trig)不能一起使用&#xff0c;否则会停不下来 void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size){RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddrAddrA;//外…

爬楼梯(c)

文章目录 描述分析思路关键代码运行结果 描述 给定一个整数数组 cost &#xff0c;其中 cost[i]是从楼梯第i 个台阶向上爬需要支付的费用&#xff0c;下标从0开始。-旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶 要求&#xff1a;请你计算并返回达到楼梯顶部的…

在使用lombok的@Slf4j时,启动项目报错。java:找不到符号

问题背景&#xff1a; 在使用lombok的Slf4j时&#xff0c;启动项目报错。 java&#xff1a;找不到符号符号&#xff1a; 方法info(java.lang.String,java.lang.String)位置&#xff1a; 类型为org.apache.ibatis.logging.Log的变量 log解决方式&#xff1a; 在如图所示位置加…

在线预约家政服务小程序上门服务源码系统 带完整的安装代码包以及搭建教程

随着互联网的快速发展&#xff0c;家政服务行业也逐渐向线上化、智能化转型。为了满足广大用户的需求&#xff0c;罗峰给大家分享一款在线预约家政服务小程序上门服务源码系统。该系统不仅功能完善&#xff0c;而且操作简单&#xff0c;是您打造高效、便捷的家政服务平台的首选…

MySql 表中的id突然变很大,如何给id重新排序

目录 一、场景 二、解决方法 一、场景 我们在开发过程中&#xff0c;难免遇到id突然增大的情况。 由于id突然增大很多&#xff0c;我们重新增加数据时候id会默认加1 那么如何让id 重新从1按顺序排序呢 二、解决方法 点击编辑表&#xff0c;然后新建一个字段id2&#xff0c;将…

探索分布式系统监控zabbix-------------监控Windows

扩展windows 10 server2012 server2016 server2019 监控 一、在虚拟机中安装zabbix的客户端 下载网站 Download and install Zabbix 安装系统一直托不进虚拟机中&#xff1b;因为没安装Tools组件 点击虚拟机&#xff0c;选择安装VMware Tools 查看主机名 二、在web页…

每天五分钟计算机视觉:基于卷积操作完成滑动窗口的图片分类?

本文重点 我们前面学习了使用不同大小的滑动窗口来滑动图片,然后切分成许多小的图片,然后依次应用到我们已经训练好的图像分类模型中,但是这种方式效率太低了,本节课程我们学习一种新的方式,来看一下如何并行识别这些剪切的图片。 原始结构 首先我们先来看一下,如何把…

InCopy2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 InCopy是一种专业的文字处理软件&#xff0c;由Adobe公司开发。它通常与Adobe InDesign一起使用&#xff0c;主要用于协作式工作流程中的文本编辑和校对。通过与InDesign的集成&#xff0c;InCopy实现了文本编辑和设计分离&#…

初步了解InnoDB存储引擎的架构设计

1. 更新语句在MySQL中是如何执行的&#xff1f; 之前我们已经分析了MySQL架构上的整体设计原理&#xff0c;现在对一条SQL语句从我们的系统层面发送到MySQL中&#xff0c;然后一步一步执行这条SQL的流程&#xff0c;都有了一个整体的了解。 我们已经知道了&#xff0c;MVSQL最…

Springboot 初始化操作

在使用Springboot过程中&#xff0c;或多或少我们会遇到在Springboot启动时要初始化类&#xff0c;或者加载文件之类的一些操作。关于初始化&#xff0c;主要分为两类&#xff0c;一类是在程序启动后的执行初始化操作&#xff0c;另一类是Bean实例化时执行初始化操作&#xff0…

3D Tiles 规范(一概述)

3D Tiles 专为流式传输和渲染大量 3D 地理空间内容而设计&#xff0c;例如摄影测量、3D 建筑、BIM/CAD、实例化要素和点云。它定义了分层数据结构和一组提供可渲染内容的Tile格式。3D Tiles 没有定义内容可视化的明确规则&#xff1b;客户可以根据自己认为合适的方式可视化 3D …

如何理解Vue 3组件的component关键字

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Verilog仿真跨模块调用内部信号的方法

在Verilog仿真时如果需要调用某子模块中的信号在本模块中使用可以使用层次化引用的方法&#xff0c;而不需要在rtl部分用端口引出来。 引用方式&#xff1a;当前例化模块名.子例化模块名.子子例化模块名.参数 将需要的信号引出。 注意是用例化模块名而不是用子模块名&#xff…