小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统

目录
前言
今日进度
详细过程
相关知识点
前言
昨天重构了数据库并实现了登录功能,今天继续进行开发,创作不易,请多多支持~
今日进度
添加过滤器、实现登出功能、实现用户授权功能校验
详细过程
一、添加过滤器
自定义过滤器作用:自定义的 JWT 认证过滤器,用于解析请求头中的 token,验证用户身份,并将用户信息存入 SecurityContextHolder,从而支持 Spring Security 的认证和授权功能。
package com.example.familyeducation.utils.filter;import com.example.familyeducation.entity.LoginUser;
import com.example.familyeducation.utils.JwtUtil;
import com.example.familyeducation.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;import static com.example.familyeducation.utils.constants.RedisConstants.LOGIN_USER_KEY;/*** @ClassDescription:自定义过滤器,获取请求头中的token并解析* @Author:小菜* @Create:2024/11/6 10:34**/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Resourceprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//1.获取请求头中的tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//2.token为空,直接放行filterChain.doFilter(request, response);return;}//3.token不为空//3.1解析token中的idString id;try {Claims claims = JwtUtil.parseJWT(token);id = claims.getSubject();} catch (Exception e) {//TODO 使用统一异常类封装throw new RuntimeException("token非法");}//4.根据id从redis中获取用户信息String key = LOGIN_USER_KEY + id;LoginUser loginUser = redisCache.getCacheObject(key);if(Objects.isNull(loginUser)){throw new RuntimeException("redis中数据为空,用户未登录");}//5.将用户信息存入SecurityContextHolder//TODO获取当前用户权限信息封装到Authentication 直接从LoginUser中获取即可//5.1封装用户信息到AuthenticationUsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//5.2将信息存入SecurityContextHolderSecurityContextHolder.getContext().setAuthentication(authenticationToken);//6.放行filterChain.doFilter(request,response);}
}
添加完过滤器后记得去将过滤器添加上
//添加自定义过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
这样访问其他接口时,就会自动解析token并进行对应操作
二、登出功能
在登出功能中我们将数据从Redis中进行删除,那样其他接口就无法访问到这些数据,在过滤器中就会显示用户为登录,实现登出功能
@Overridepublic ResponseResult logout() {//1.从SecurityContextHolder中查找到AuthenticationAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();//2.从Authentication中获取LoginUserLoginUser loginUser = (LoginUser) authentication.getPrincipal();//3.获取id并从Redis中删除,那样下次再进行过滤时就查询不到Redis中的数据,显示用户未登录Integer userId = loginUser.getUser().getId();redisCache.deleteObject(LOGIN_USER_KEY+userId);return new ResponseResult(200,"成功退出登录");}
三、权限校验
权限校验今天踩了很多坑,因为之前没有接触过这个
在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。
首先,权限校验的思路如下:
- 请求先到过滤器,此时的请求中携带token
- 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
- 过滤器将数据存到SecurityContextHolder,这样请求进行过程中就能得到数据,可以来判断是否有权限,若无权限则返回403
首先我们先去配置中配置一下
我们要开启一下配置并指定路径与权限的关系,指定哪些路径需要哪些权限才能访问
package com.example.familyeducation.config;import com.example.familyeducation.utils.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
//@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous().antMatchers("/hello/**").hasRole("ADMIN")//对于/hello的路径,只有ADMIN权限的用户才能访问.antMatchers("/ok/**").hasAnyRole("ADMIN","USER")//对于/ok的路径,ADMIN和USER权限的用户都可以访问// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加自定义过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}}
配置完后,因为登录成功后才能得到用户数据和权限,所以我们要去UserDetailsServiceImpl中完成之前的TODO将用户权限信息添加到Login中
package com.example.familyeducation.service.impl;import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.familyeducation.entity.LoginUser;
import com.example.familyeducation.entity.User;
import com.example.familyeducation.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** @ClassDescription:* @Author:小菜* @Create:2024/11/4 19:06**///这里继承的是security中的一个默认接口,重写其中的查询用户方法
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询用户信息LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUsername,username);User user = userMapper.selectOne(wrapper);//如果查询不到数据就通过抛出异常来给出提示if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}//TODO根据用户查询权限信息 添加到LoginUser中//上面的user信息中已经包含了权限信息,但是我们还是要单独把权限提出来List<String> list = new ArrayList<>();//这里list就是LoginUser中的redisAuthoritiesString role = user.getRole();if(role.equals("admin")){list.add("ROLE_ADMIN");//注意这里添加自定义权限时要加前缀ROLE_,SpringSecurity会默认根据ROLE_去查找权限} else if (role.equals("teacher")) {list.add("ROLE_TEACHER");}//封装成UserDetails对象返回return new LoginUser(user,list);}
}
同时在LoginUser中我们要进行authorities的管理并编写getAuthorities()方法保证过滤器能获取到用户权限
package com.example.familyeducation.entity;import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;private List<String> redisAuthorities;public LoginUser(User user, List<String> redisAuthorities) {this.user=user;this.redisAuthorities=redisAuthorities;}@JSONField(serialize = false)//保证该集合不被序列化private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}else{authorities = redisAuthorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
最后我们使用Apifox进行测试
先登录一下,成功返回了token,同时Redis中保存了用户信息和权限


好,接下来对权限进行测试,hello路径是只有ADMIN能进行访问,而我们当前用户刚好是管理员,所以得到了返回信息,测试成功!

相关知识点
登录功能思路:
- 请求先到过滤器,过滤器检查到token为空,直接放行
- 放行后请求到controller层,并调用service层的实现类LoginServiceImpl
- Service实现类LoginServiceImpl中会对认证信息进行验证,于是调用SpringSecurity中的authenticationManager.authenticate()方法进行检验
- 这个方法会自动调用UserDetailsService中的loadUserByUsername进行用户信息验证
- 在UserDetailsService中进行用户数据查询,同时将用户信息中的权限进行封装
- 最后将用户信息和权限信息封装成LoginUser进行返回到登录实现类LoginServiceImpl中
- 实现类LoginServiceImpl根据返回的信息生成token返回前端,并将loginUser存到Redis
实现授权功能思路:
- 请求先到过滤器,此时的请求中携带token
- 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
- 过滤器将数据存到SecurityContextHolder,这样请求进行过程中就能得到数据,可以来判断是否有权限,若无权限则返回403
这里有两个坑,一个是权限能获取到,但是保存到Redis中一直是null,另一个是成功保存了权限,但是测试时一直显示403。
我们先解决第一个问题,在封装信息时我们会调用LoginUser中的一个获取权限的方法,这里一开始是定义了一个authorities并直接返回,但是就会有一个问题,当一个新方法调用时,authorities会被重新刷新为null,就导致权限信息一直是null,解决方法是定义一个新的List,并将其保存到Redis中,那样调用新方法,我们在过滤器中获取Redis中的权限,就不会是null了。
private User user;private List<String> redisAuthorities;public LoginUser(User user, List<String> redisAuthorities) {this.user=user;this.redisAuthorities=redisAuthorities;}@JSONField(serialize = false)//保证该集合不被序列化private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}else{authorities = redisAuthorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}}
第二个问题,这个真的给我整无语了,Spring Security 会通过 GrantedAuthority 来验证用户是否拥有特定的权限。例子中,用户访问 /ok/** 时,Spring Security 会检查 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 中是否包含 ROLE_ADMIN 或 ROLE_USER。
- Spring Security 的
hasRole()方法默认会自动在角色前加上ROLE_前缀,所以当你配置.hasRole("ADMIN")时,实际上是在检查用户是否有ROLE_ADMIN权限。 - 如果用户的权限中有
ROLE_ADMIN或ROLE_USER,则通过访问检查,允许访问这个路径,否则拒绝访问。
所以就是在手动封装权限的时候没有加上前缀导致权限信息一直对不上,所以就显示403了。。。
List<String> list = new ArrayList<>();//这里list就是LoginUser中的redisAuthoritiesString role = user.getRole();if(role.equals("admin")){list.add("ROLE_ADMIN");//注意这里添加自定义权限时要加前缀ROLE_,SpringSecurity会默认根据ROLE_去查找权限} else if (role.equals("teacher")) {list.add("ROLE_TEACHER");}
ok,大概就是这样,如果有帮到你的话,请多多支持哦!你的鼓励就是我最大的动力,我们下篇再见~
相关文章:
小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统
目录 前言 今日进度 详细过程 相关知识点 前言 昨天重构了数据库并实现了登录功能,今天继续进行开发,创作不易,请多多支持~ 今日进度 添加过滤器、实现登出功能、实现用户授权功能校验 详细过程 一、添加过滤器 自定义过滤器作用&…...
ArcGIS/QGIS按掩膜提取或栅格裁剪后栅格数据的值为什么变了?
问题描述: 现有一栅格数据,使用ArcGIS或者QGIS按照矢量边界进行按掩膜提取或者栅格裁剪以后,其值的范围发生了变化,如下: 可以看到,不论是按掩膜提取还是进行栅格裁剪后,其值的范围均与原来栅…...
Linux的基本指令(一)
1.ls指令 功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及信息。 常用选项: -a列出目录下的所有文件,包括以 . 开头的隐含文件。 -l列出文件的详细信息 举例: rooti…...
python导入包失败 in <module> import pandas as pd
如果安装不成功就更新一下pip python.exe -m pip install --upgrade pip 再删掉原来的pandas pip uninstall pandas 再安装一次 pip install pandas...
不惧风雨,硬核防护!雷孜LaCie小金刚三防移动硬盘颠覆认知
不惧风雨,硬核防护!雷孜LaCie小金刚三防移动硬盘颠覆认知 哈喽小伙伴们好,我是Stark-C~ 说到移动硬盘大家潜意识的认为是一件很娇贵的数码产品,很怕湿,摔不得。所以我们在使用传统移动硬盘的时候不能摔,远…...
Yocto 项目下通过网络更新内核、设备树及模块
Yocto 项目下通过网络更新内核、设备树及模块 前言 在 Yocto 项目的开发过程中,特别是在进行 BSP(Board Support Package)开发时,经常需要调整特定软件包的版本,修改内核、设备树以及内核模块。然而,每次…...
Scheduled Sampling工作原理【小白记笔记】
Scheduled Sampling(计划采样)是一种在序列生成任务中用于逐步引导模型的训练策略。该方法最早由 Bengio 等人在 2015 年提出,主要用于解决序列到序列(sequence-to-sequence)模型中的曝光偏差(exposure bia…...
C++:C++的IO流
目录 一.C标准IO流 1.operator bool 二.C文件IO流 1.文件读取 ifstream (1)ifstream继承istream (2)ifstream 构造函数 (3)ifstream,get读取整个文件 (4)>&g…...
「QT」几何数据类 之 QLine 整型直线类
✨博客主页何曾参静谧的博客📌文章专栏「QT」QT5程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…...
day58 图论章节刷题Part09(dijkstra(堆优化版)、Bellman_ford 算法)
dijkstra(堆优化版) 朴素版的dijkstra解法的时间复杂度为 O(n^2),时间复杂度只和 n(节点数量)有关系。如果n很大的话,可以从边的角度来考虑。因为是稀疏图,从边的角度考虑的话,我们在堆优化算法中最好使用…...
【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】试卷(1)
前言 大家好吖,欢迎来到 YY 滴计算机网络 系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 本博客主要内容,收纳了一部门基本的计算机网络题目,供yy应对期中考试复习。大家可以参考 本章是去答案版本。带答案的版本在下…...
智能出行助手:SpringBoot共享汽车管理平台
1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理共享汽车管理系统的相关信息成为必然。开发…...
【月之暗面kimi-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 …...
Flink实现实时数据处理
代码如下: #!/usr/bin/python # -*- coding: UTF-8 -*-from pyflink.datastream import StreamExecutionEnvironment from pyflink.table import StreamTableEnvironment, EnvironmentSettings, DataTypes# 初始化执行环境 s_env StreamExecutionEnvironment.get_…...
11.9.2024刷华为
文章目录 HJ31 单词倒排HJ32 密码提取语法知识记录 傻逼OD题目又不全又要收费,看毛线,莫名奇妙 HW这叼机构别搁这儿害人得不得? 我觉得我刷完原来的题目 过一遍华为机考的ED卷出处,就行了 HJ31 单词倒排 游戏本做过了好像 HJ3…...
Chromium 中chrome.system.storage扩展接口定义c++
一、chrome.system.storage 您可以使用 chrome.system.storage API 查询存储设备信息,并在连接和分离可移动存储设备时收到通知。 权限 system.storage 类型 EjectDeviceResultCode 枚举 "success" 移除命令成功执行 - 应用可以提示用户移除设备。…...
【Qt聊天室客户端】登录窗口
1. 验证码 具体实现 登录界面中创建验证码图片空间,并添加到布局管理器中 主要功能概述(创建一个verifycodewidget类专门实现验证码操作) 详细代码 // 头文件#ifndef VERIFYCODEWIDGET_H #define VERIFYCODEWIDGET_H#include <QWidget>…...
如何显示模型特征权重占比图【数据分析】
可视化模型的特征权重 1、流程 1、导入库: numpy:用于处理数组和矩阵。 matplotlib.pyplot:用于绘图。 sklearn.datasets:用于加载数据集。 sklearn.ensemble.RandomForestClassifier:用于训练随机森林模型。2、加载数据集: 使用load_iris函数加载Iris数据集。3、训练模…...
Ubuntu24安装MySQL
下载deb包: 先更新系统包: sudo apt update sudo apt update -y下载mysql: wget https://dev.mysql.com/get/mysql-apt-config_0.8.17-1_all.deb 安装deb包: sudo dpkg -i mysql-apt-config_0.8.17-1_all.deb目前mysql还没有正式支持Ubun…...
微服务架构面试内容整理-Eureka
Spring Cloud Netflix 是一个为构建基于 Spring Cloud 的微服务应用提供的解决方案,利用 Netflix 的开源组件来实现常见的分布式系统功能。以下是 Spring Cloud Netflix 的一些主要组件和特点: 服务注册与发现:Eureka 是一个 RESTful 服务,用于注册和发现微服务。服务实例在…...
AI光互连商POET订单骤停,近半市值蒸发!供应链保密红线敲响警钟
订单骤停:一纸违约通知,四成市值蒸发美东时间4月27日,光互连方案商POET Technologies股价单日暴跌47.25%,收报7.95美元,盘中一度触及7.50美元低位。上周五,这家公司刚刚创下15.50美元的11年新高,…...
告别Keil/IAR:用VSCode+GCC为STM32移植OpenHarmony LiteOS-M的踩坑与收获
从Keil到VSCode:STM32移植OpenHarmony LiteOS-M的工程实践 当传统嵌入式开发环境遇上现代工具链,会碰撞出怎样的火花?三年前我接手一个工业控制器项目时,首次尝试用VSCodeGCC替代Keil进行STM32开发,从此再没打开过那些…...
机械设备钢材建材网站 网站模版
内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示一、详细介绍 这款网站源码采用PHPMySQL/Sqlite的经典技术组合,两种技术的优势互补,为网站稳定运行提供坚实保障。PHP 语言具备开发效率高、跨平台性强的特点,能够快速实现各类功能需…...
详解C++中动态内存管理和泛型编程
一、C/C内存区域划分1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。3. 堆用于程序运行时动态内存…...
OpenClaw Forge:基于身份张力与文化原型的AI角色深度锻造方法论
1. 项目概述:从零到一,打造有“灵魂”的AI角色如果你也和我一样,在尝试为Claude Code(或者任何AI助手)设计一个真正有深度、有记忆点的“角色”时,感到无从下手,那么OpenClaw Forge这个项目&…...
大语言模型奉承偏见的成因与优化策略
1. 大语言模型中的奉承偏见现象剖析最近在调试对话系统时发现一个有趣现象:当用户提出明显错误的观点时,模型往往会表现出过度迎合倾向。比如测试者故意说"地球是平的",多数开源模型会回应"这是个有趣的观点"而非纠正错误…...
Vibe Stack:用架构规则约束AI编程,解决Next.js与Supabase开发中的安全与兼容性问题
1. 项目概述:用架构规则为AI编程上“紧箍咒” 如果你和我一样,已经深度依赖像 Cursor、GitHub Copilot 或 Claude 这样的 AI 编程助手来加速开发,那你一定也经历过那种“冰火两重天”的体验。一方面,生产力确实得到了前所未有的提…...
别再只盯着特斯拉了!聊聊吉利、小鹏、岚图都在用的‘域控制器’到底是个啥?
从吉利到小鹏:域控制器如何重塑你的智能驾驶体验? 当你在展厅里被吉利星越L的自动泊车功能吸引,或是被小鹏P7的智能座舱震撼时,可能不会想到这些体验背后都藏着一个关键技术——域控制器。这就像智能手机从功能机进化时࿰…...
Packet Tracer 中文语言包安装指南
思科模拟器(特别是Cisco Packet Tracer)可以通过安装中文语言包,将软件界面、菜单及部分设备注释切换为中文 。其核心设置方法是通过替换语言文件并修改软件首选项来实现。 1. 思科模拟器中文化方法详解 思科Packet Tracer的中文化并非通过…...
调试 jar 包时遇到 exec 命令(或进程)一直转圈(挂起/无响应)
在使用IDEA调试项目时,莫名遇到有一个exec命令一直转圈,点击结束调试时,这个命令也一直在转圈,没有停止。以前从来没有遇见过,仔细看启动控制台的启动命令,出现了agentlib、dt_socket、exec-maven-plugin等…...
