超详细的前后端实战项目(Spring系列加上vue3)前后端篇(四)(一步步实现+源码)

兄弟们,继昨天的代码之后,继续完成最后的用户模块开发,

昨天已经完成了关于用户的信息编辑页面这些,今天再完善一下,

从后端这边开始吧,做一个拦截器,对用户做身份校验,

拦截器

这边先解释一下吧:

那就再用面试题来细说:

拦截器相关面试题:过滤器与拦截器有什么区别?

答:

  • 一:运行的顺序不同
    • 过滤器是servlet容器接收到请求之后,在servlet被调用之前运行的
    • 拦截器则是在servlet被调用之后,但是在响应被发送到客户端之前运行的
  • 二:配置方式不同
    • 过滤器是在web.xml配置
    • 拦截器是在spring的配置文件中配置,或者基于注解进行配置
  • 三:依赖关系
    • Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器
  • 四:能力方面
    • Filter在过滤器中只能对request和response进行操作
    • Interceptor可以对request,response,handler,modelAndView,exception,相当于Interceptor多了对SpringMvc生态下组件的一个操作能力
  • 接口规范不同
    • 过滤器需要实现Filter接口,拦截器需要实现HandlerInterceptor接口
  • 拦截范围不同
    • 过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

OK,来做一个拦截器:

创建一个interceptors包,并创建LoginInterceptor类

拦截器的类要继承

HandlerInterceptor

注:

首先,创建拦截器类时,需要实现HandlerInterceptor接口。这个接口定义了三个方法:preHandle()postHandle()afterCompletion()preHandle()方法是在控制器方法执行之前调用的,它返回一个布尔值,表示是否继续执行后续操作;postHandle()方法是在控制器方法执行之后,视图解析之前调用的,可以用来对响应进行进一步的处理;afterCompletion()方法是在整个请求处理完成之后调用的,通常用于资源的清理工作。

其次,除了实现HandlerInterceptor接口,还可以选择继承HandlerInterceptorAdapter类来简化拦截器的实现。HandlerInterceptorAdapter提供了HandlerInterceptor接口的空实现,使得开发者只需要重写自己关心的方法即可。

最后,为了让拦截器生效,还需要在Spring配置文件中进行相应的配置。这通常涉及到定义一个配置类,实现WebMvcConfigurer接口,并重写addInterceptors()方法。在这个方法中,可以使用addPathPatterns()来指定拦截路径,使用excludePathPatterns()来指定排除的路径。

在实际开发中,根据需求选择合适的拦截器类型是非常重要的。例如,如果需要在Controller层进行权限验证,那么使用HandlerInterceptor接口是合适的;如果只是对请求进行简单的预处理和后处理,那么可以考虑使用WebRequestInterceptor接口。选择合适的拦截器类型可以确保代码的整洁性和可维护性。

下面给出代码

import org.example.cetidenet.utils.JwtUtil;
import org.example.cetidenet.utils.ThreadLocalUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return false;
}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

这里继承HandlerInterceptor之后按快捷键Ctrl+o,然后继承三种方法就行了,这里常用的是第一种preHandle,在请求到Controller层之前会被preHandle拦截并处理,如果返回不为true,则会被拦截。

创建了拦截器之后还要注册拦截器。

这里就在之前创建的config包下进行操作

在之前加载Swagger资源的类下加上代码:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//登录接口和注册接口不拦截registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/doc.html#/home");}

这里有个小坑(之前单独创建了一个类继承的是WebMvcConfigurer,结果拦截器缺没有起作用,如果是这样,就继承WebMvcConfigueationSupport)

添加了拦截的的路径和放行的路径之后就可以进行检验了

OK,这里使用拦截器之后,登录接口可以正常使用,

而使用更新接口则返回401没有权限。那也就成功了。

JWT令牌:

那么使用了拦截器之后拦截了除了登录注册之外的其他的接口,那该如何才能让其使用呢,必然需要验证身份,这里可以使用JWT令牌,

也就是在用户登录完成之后发放JWT令牌,然后反馈给前端,前端携带其到后端,拦截器对其进行校验,如果带有JWT令牌就放行。

后端引入JWT令牌依赖

       <!--java-jwt坐标--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {private static final String KEY = "CeTide";//接收业务数据,生成token并返回public static String genToken(Map<String, Object> claims) {return JWT.create().withClaim("claims", claims).withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 )).sign(Algorithm.HMAC256(KEY));}//接收token,验证token,并返回业务数据public static Map<String, Object> parseToken(String token) {return JWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim("claims").asMap();}}

然后在登录接口这里添加代码:

    public Result<String> login(UserLoginDTO userLoginDTO) {//先获取DTO中的账号面膜String username = userLoginDTO.getUserName();String password = userLoginDTO.getPassword();//先查询数据库中是否有这号人物User user = userMapper.findByUsername(username);//判断是否存在if(user == null){return Result.error("该用户不存在");}String salt = password + "ceTide";String pwd = DigestUtils.md5Hex(salt.getBytes()).toUpperCase();//存在,判断密码是否正确if(!user.getPassword().equals(pwd)){return Result.error("用户密码错误");}
//        boolean isLog = logService.addUserLogin(user);
//        if(!isLog){
//            return Result.error("用户登录日志记录失败");
//        }//登录成功Map<String, Object> claims = new HashMap<>();claims.put("id", user.getId());claims.put("username", user.getUserName());String token = JwtUtil.genToken(claims);//存在且密码正确return Result.success(token);}

将用户的id和username放入到token中(虽然但是,尽量不要把密码放进去)

然后在拦截器这里进行校验:

    @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//令牌验证System.out.println("开始验证");String token = request.getHeader("Authorization");//验证令牌try{Map<String,Object> claims = JwtUtil.parseToken(token);//放行return true;}catch (Exception e) {//反馈响应码401response.setStatus(401);return false;}}

这里如果能够解析就代表拥有JWT令牌,那也就可以放行了

现在回到前端这里:

先看一下修改后的前端代码

<template><div id="userCenter"><a-layout style="height: 100vh"><a-layout-header><a-page-header title="用户中心" subtitle="CeTide网" @click="returnPage"/><div class="header"><div class="user-introduce"><img:src="userNum.userImg"width="70px"height="70px"class="user-img"/><div><div class="personal-introduce"><div style="margin-left: 10px"><span class="name">{{ userNum.userName }}</span><span class="sex-icon"></span></div><a-space class="btn"><a-button type="dashed" shape="round" @click="handleClick"><icon-pen-fill />编辑资料</a-button><a-button type="dashed" shape="round"><icon-settings />设置</a-button><a-button type="dashed" shape="round"><icon-list />文章管理</a-button></a-space></div></div></div><div class="user-follow">{{ userNum.attention }}<icon-star /><span class="follow-num">关注</span>{{ userNum.fans }}<icon-heart /><span class="follow-num">粉丝</span>{{ userNum.article }}<icon-select-all /><span class="follow-num">文章</span></div><div class="user-follow">个人简介:{{ userSelfIntroduce }}</div></div></a-layout-header><a-layout style="margin: 24px 180px"><a-layout-sider :resize-directions="['right']"><a-layoutstyle="height: 100%;text-align: left;padding-left: 20px;background-color: #c4c4c4;"><a-layout-content style="height: 20%"><h3>CeTide等级</h3></a-layout-content><a-layout-content style="height: 20%"><h3>个人成就</h3></a-layout-content><a-layout-content style="height: 60%"><h3>个人动态</h3></a-layout-content></a-layout></a-layout-sider><a-layout-content class="content"><h3>用户中心</h3></a-layout-content></a-layout><a-layout-footer>Footer</a-layout-footer></a-layout><!-- 编辑个人信息的抽屉 --><a-drawer:visible="visible":width="500"@ok="handleOk"@cancel="handleCancel"unmountOnClose><template #title> 编辑个人信息 </template><div :style="{ marginBottom: '20px' }"><div ><img :src="userNum.userImg" width="70px" height="70px" class="user-img"/><a-button type="primary" @click="handleNestedClick" style="float: right;margin-top: 20px">更换头像</a-button></div><a-divider /><div> 用户名:<a-input :style="{width:'320px'}" allow-clear v-model="userNum.userName"/></div><a-divider /><div> 性别:<a-input :style="{width:'320px'}" v-model="numSex" /></div><a-divider /><div> 电话:<a-input :style="{width:'320px'}" v-model="userNum.phone"/></div><a-divider /><div> 生日:<a-input :style="{width:'320px'}" v-model="userNum.birthday" /></div><a-divider /><div> 城市:<a-input :style="{width:'320px'}" v-model="userNum.county" /></div><a-divider /><div> 住址:<a-input :style="{width:'320px'}" v-model="userNum.address" /></div><a-divider /><div> CeTide网ID:<a-input :style="{width:'320px'}" v-model="userNum.id" disabled/></div><a-divider /><div> 个人简介: <a-textarea v-model="userSelfIntroduce" allow-clear style="height: 100px"/></div></div></a-drawer><a-drawer:visible="nestedVisible"@ok="handleNestedOk"@cancel="handleNestedCancel"unmountOnClose><template #title> 文件操作 </template><a-space direction="vertical" :style="{ width: '100%' }" class="picture"><a-uploadaction="/":fileList="file ? [file] : []":show-file-list="false"@change="onChange"@progress="onProgress"><template #upload-button><div:class="`arco-upload-list-item${file && file.status === 'error' ? ' arco-upload-list-item-error' : ''}`"><divclass="arco-upload-list-picture custom-upload-avatar"v-if="file && file.url"><img :src="file.url" /><div class="arco-upload-list-picture-mask"><IconEdit /></div><a-progressv-if="file.status === 'uploading' && file.percent < 100":percent="file.percent"type="circle"size="mini":style="{position: 'absolute',left: '50%',top: '50%',transform: 'translateX(-50%) translateY(-50%)',}"/></div><div class="arco-upload-picture-card" v-else><div class="arco-upload-picture-card-text"><IconPlus /><div style="margin-top: 10px; font-weight: 600">Upload</div></div></div></div></template></a-upload></a-space></a-drawer></div>
</template><script setup>
import {userGetInfo} from '../api/user';
import { ref } from "vue";
import avatar from "../assets/userbg.png";
import { useRouter } from "vue-router";
const router = useRouter();
const userInfoList= async()=>{let result = await userGetInfo();userNum.value = result.data;
}
userInfoList();
const userSelfIntroduce = ref("这个人很懒,什么都没有留下");
const userNum = ref({id: "007",county: "四川",address: "成都",phone: "12345678910",birthday: "1999-09-09",gender: "女",email: "123@qq.com",userImg: avatar,userName: "我是小丑",attention: 0,fans: 0,article: 0,
});
const numSex = ref(userNum.value.gender === 'F' ? '男' : '女');
//抽屉显示隐藏
const visible = ref(false);
const nestedVisible = ref(false);const handleClick = () => {visible.value = true;
};
const handleOk = () => {visible.value = false;
};
const handleCancel = () => {visible.value = false;
};
const handleNestedClick = () => {nestedVisible.value = true;
};
const handleNestedOk = () => {nestedVisible.value = false;
};
const handleNestedCancel = () => {nestedVisible.value = false;
};//返回方法
const returnPage = () =>{router.push('/')
}//
</script><style lang="scss" scoped>
#userCenter {background: url("../assets/image.png") no-repeat bottom center / 100% 100%;
}
.header {font-family: "Satisfy", cursive;margin: 2% 100px;height: 20vh;background: url("../assets/back.png") no-repeat center / 100% 100%;position: relative;
}.personal-introduce {display: flex;justify-content: center;align-items: flex-end;margin-top: 10px;text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.31);.name {line-height: 29px;font-size: 26px;}.sex-icon {display: inline-block;width: 16px;height: 16px;margin: 0px 8px;margin-bottom: 4px;background: url(../assets/user-images/sex-icon.png) no-repeat center;background-size: contain;border-radius: 50%;}.level-icon {display: inline-block;width: 16px;height: 16px;margin-bottom: 4px;background: url(../assets/user-images/leval-icon.png) no-repeat center;background-size: contain;border-radius: 50%;}
}.user-introduce {display: flex;justify-items: left;padding: 10px;
}
.user-img {border-radius: 50%;margin-left: 20px;
}
.user-follow {margin-left: 30px;font-size: 16px;display: flex;justify-items: left;
}
.follow-num {font-size: 16px;padding-right: 20px;
}
.content {margin-left: 70px;background-color: #c4c4c4;
}
.btn {position: absolute;right: 40px;
}</style>

在登录之后获取到了JWT值

“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjE1LCJ1c2VybmFtZSI6IueUqOaIt1VTRERBRCJ9LCJleHAiOjE3MTY4MDQ4ODB9._9GDxuux5wmoV5CsZCd0QI3wByESKWGGZCKmDaZVlbc”

这样一大串字,然后前端将其放置在请求头的Authorization属性中

举个例子

export const userGetInfo= () =>{return request.get('/user/getInfo',{headers : {'Authorization' : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjE1LCJ1c2VybmFtZSI6IueUqOaIt1VTRERBRCJ9LCJleHAiOjE3MTY4MDQ4ODB9._9GDxuux5wmoV5CsZCd0QI3wByESKWGGZCKmDaZVlbc',}});}

此时访问页面,就能得到用户名等信息了

然后就算是成功实现了请求的拦截与用户身份的检验

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

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

相关文章

OrangePi AIpro 性能测试以及使用体验

OrangePi AIpro 性能测试以及使用体验 1. 介绍 OrangePi AIpro(8T)采用昇腾AI技术路线。 具体为4核64位处理器AI处理器&#xff0c;集成图形处理器&#xff0c;支持8TOPS AI算力拥有8GB/16GB LPDDR4X&#xff0c;可以外接32GB/64GB/128GB/256GB eMMC模块&#xff0c;支持双4…

输出相关命令

什么是输入输出重定向&#xff0c;就是用另外一个位置来代替它&#xff0c;默认输入为键盘&#xff0c;默认输出为终端窗口 管道能把一系列的命令连起来&#xff0c;&#xff5c;为命令符 cat file 历史查询 history 回车可以查到用过的命令。上下左右键可以回到之前命令或…

记mapboxGL实现鼠标经过高亮时的一个问题

概述 mapboxGL实现鼠标经过高亮可通过注册图层的mousemove和moveout事件来实现&#xff0c;在mousemove事件中可以拿到当前经过的要素&#xff0c;但是当使用该要素时&#xff0c;发现在某个地图级别下会有线和面数据展示不全的情况。究其原因&#xff0c;发现是mapboxGL在绘图…

【多线程】线程安全

目录 1.观察线程不安全 2.线程不安全的原因 2.1 随机调度 2.2 修改共享数据 2.3 原子性 2.4 内存可见性 2.5 指令重排序 3.synchronized 加锁操作 3.1 synchronized是什么&#xff1f; 3.2 synchronized的特性 1) 互斥 2) 可重入 3.3 synchronized使用示例 3.3.1 针…

“二叉堆:不是,啊?”

目录 前言一、堆的概念及结构堆的性质&#xff1a;堆的结构:最大堆最小堆堆顶注意 二、堆的实现1.初始化堆2. 堆的插入什么是堆的向上调整算法&#xff1f; 3.堆的删除什么是堆的向下调整算法&#xff1f; 4.获取堆顶的数据5.获取堆的数据个数6.堆的判空7.堆的销毁 三、建堆的时…

【ES6】ECMAS6新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【JavaScript】 文章目录 &#x1f33f; 引言一、 let 和 const - 变量声明的新方式 &#x1f31f;&#x1f4cc; var的问题回顾&#x1f4cc; let的革新&#x1f4cc; const的不变之美 二、 Arro…

第六十六节 Java设计模式 -责任链模式

Java设计模式 -责任链模式 责任链模式为请求创建一个接收者对象列表。 这种模式是行为模式。 当使用责任链模式时&#xff0c;通常每个接收器包含对另一个接收器的引用。 如果一个对象不能处理请求&#xff0c;则它将相同的对象传递给下一个接收者&#xff0c;等等。 例子 …

删除edge浏览器文本框储存记录值以及关闭自动填充

当我们点击输入框时总会出现许多以前输入过的信息。 一、删除edge浏览器文本框储存记录值 1、首先按下↓键选中你想删除的信息 二、关闭自动填充。 1、在地址栏输入edge://wallet/settings跳转到以下界面 2、往下滑找到 全部取消即可

你也许不知道,自己可能是一个热人

今天想跟大家分享的&#xff0c;是一种很少有人了解的人格特质。它非常普遍&#xff0c;许多人都或多或少有一些倾向&#xff0c;但却很少有人意识到它。 不妨看一看&#xff0c;你有没有下面这些特征&#xff1a; 有着极其旺盛的求知欲&#xff0c;对许多奇奇怪怪的问题都有着…

nvm安装教程及使用nvm管理多个node版本

文章目录 前言一、nvm 安装教程温馨提示macOS/LinuxWindows 二、安装 node 前言 工作中&#xff0c;你可能会遇到以下场景&#xff1a; 我想使用 pnpm 命令安装依赖&#xff0c;但是在使用 pnpm 命令时提示如下 $ pnpm -v ERROR: This version of pnpm requires at least No…

捷报!恒瑞医药ADC创新药SHR-A1921卵巢癌适应症拟纳入突破性治疗品种公示

近日&#xff0c;恒瑞医药自主研发的TROP-2抗体偶联药物&#xff08;antibody-drug-conjugate, ADC&#xff09;注射用SHR-A1921用于治疗铂耐药复发上皮性卵巢癌、输卵管癌或原发性腹膜癌适应症被国家药品监督管理局药品审评中心拟纳入突破性治疗品种公示名单。今年3月&#xf…

第五天 从零开始构建基于Kubernetes的DevOps平台

基于Kubernetes的DevOps平台实践 持续集成工具&#xff1a; JenkinsgitlabciTekton 本章基于k8s集群部署gitlab、sonarQube、Jenkins等工具&#xff0c;并把上述工具集成到Jenkins中&#xff0c;以Django项目和SpringBoot项目为例&#xff0c;通过多分支流水线及Jenkinsfile…

工业4.0 企业级云MES全套源码,支持app、小程序、H5、台后管理端

工业4.0 企业级云MES全套源码&#xff0c;支持app、小程序、H5、台后管理端 采用javaspringboot-vue.jsuniapp开发 随着工业4.0的快速发展&#xff0c;制造执行系统&#xff08;MES&#xff09;成为了智能制造的核心。今天&#xff0c;将为大家介绍一款开源的MES系统——MES管…

本周 MoonBit 核心库进行 API 整理工作、工具链持续完善

MoonBit更新 【核心库 Breaking】核心库进行API整理工作 所有immutable数据结构被放在immut路径下&#xff0c;如immutable_hashmap.Map变为immut/hashmap.Map // Before let a : immutable_hashmap.Map[Int, Int] immutable_hashmap.make() // After let a : immut/hashma…

我的创作纪念日——我与CSDN一起走过的128天

目录 一、机缘&#xff1a;旅程的开始 二、收获&#xff1a;沿路的花朵 三、日常&#xff1a;不断前行中 四、成就&#xff1a;一点小确幸 五、憧憬&#xff1a;梦中的重点 一、机缘&#xff1a;旅程的开始 最开始开始写博客是在今年一二月份的时候&#xff0c;也就是寒假…

如何自学制作电子画册,这个秘籍收藏好

随着数字技术的飞速发展&#xff0c;电子画册作为一种新兴的媒体展示形式&#xff0c;以其独特的魅力和丰富的表现手法&#xff0c;受到了越来越多人的喜爱。那么&#xff0c;如何自学制作电子画册呢&#xff1f; 1. 学习基础知识 首先&#xff0c;你需要了解电子画册的基本构…

SAP SCU0 比较两个环境之间的SPRO配置差异

TCODE : SCU0 直接选择所有 因为对比全部&#xff0c;所以会有点久&#xff0c;慢慢等着&#xff0c;吃个橘子 输入正式环境的账号密码

YonBuilder移动开发基础教程——云修复

1 使用场景 在项目开发中&#xff0c;我们经常会遇到一种场景&#xff0c;对于一些已经上架应用市场对外发布的应用&#xff0c;我们需要修改其中部分页面的部分内容样式或功能逻辑&#xff0c;通常的做法是我们修改后&#xff0c;重新编译一个新的版本&#xff0c;然后提交应…

OrangePi Kunpeng Pro 开发板测评及Python开发实测

一、背景 首先感谢 创新乐知通过CSDN 邀请本人&#xff0c;参与这次 评测活动。这块开发板是香橙派联合华为精心打造&#xff0c;具有超强算力的鲲鹏开发板。本人使用最多的还是树莓派系列的板子&#xff0c;国产板子特别是华为为核心的板子还是头一次使用&#xff0c;特别感兴…

BurpSuite详细安装配置教程

环境条件&#xff1a;windows7或者windows10虚拟机 一、BurpSuite安装与配置 1、BurpSuite简介 BurpSuite(简称Burp)是基于Java开发的Web安全领域的集成工具&#xff0c;被称为信息安全界的瑞士军 刀&#xff0c;它包含Proxy、Intruder、Repeater、Decoder、Comparer等多个…