小菜家教平台(三):基于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,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

首先,权限校验的思路如下:

  1. 请求先到过滤器,此时的请求中携带token
  2. 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
  3. 过滤器将数据存到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能进行访问,而我们当前用户刚好是管理员,所以得到了返回信息,测试成功!

相关知识点

登录功能思路:

  1. 请求先到过滤器,过滤器检查到token为空,直接放行
  2. 放行后请求到controller层,并调用service层的实现类LoginServiceImpl
  3. Service实现类LoginServiceImpl中会对认证信息进行验证,于是调用SpringSecurity中的authenticationManager.authenticate()方法进行检验
  4. 这个方法会自动调用UserDetailsService中的loadUserByUsername进行用户信息验证
  5. 在UserDetailsService中进行用户数据查询,同时将用户信息中的权限进行封装
  6. 最后将用户信息和权限信息封装成LoginUser进行返回到登录实现类LoginServiceImpl中
  7. 实现类LoginServiceImpl根据返回的信息生成token返回前端,并将loginUser存到Redis

实现授权功能思路:

  1. 请求先到过滤器,此时的请求中携带token
  2. 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
  3. 过滤器将数据存到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_ADMINROLE_USER

  • Spring Security 的 hasRole() 方法默认会自动在角色前加上 ROLE_ 前缀,所以当你配置 .hasRole("ADMIN") 时,实际上是在检查用户是否有 ROLE_ADMIN 权限。
  • 如果用户的权限中有 ROLE_ADMINROLE_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,大概就是这样,如果有帮到你的话,请多多支持哦!你的鼓励就是我最大的动力,我们下篇再见~

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

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

相关文章

SQL进阶技巧:如何计算复合增长率?

目录 0 场景描述 1 数据准备 2 问题分析 3 小结 0 场景描述 复合增长率是第N期的数据除以第一期的基准数据&#xff0c;然后开N-1次方再减去1得到的结果。假如2018年的产品销售额为10000&#xff0c;2019年的产品销售额为12500&#xff0c;2020年的产品销售额为15000&…

python-docx -- 读取word图片

文章目录 概念介绍形状对象读取图片自定义图形 概念介绍 从概念上来讲&#xff0c;word文档分为两层&#xff0c;一个文本层&#xff0c;一个绘画层&#xff1b; 文本层&#xff0c;从上到下&#xff0c;从左到右&#xff0c;流式排版&#xff0c;本页填满则开启新页面&#…

Python数据可视化seaborn

产品经理在做数据分析时可能需要通过可视化来分析。seaborn官网 1. relplot 散点图 https://seaborn.pydata.org/examples/scatterplot_sizes.html import pandas as pd import seaborn as sns df pd.DataFrame({x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],y: [8, 6, 7, 8, 4, 6,…

释放专利力量:Patently 如何利用向量搜索和 NLP 简化协作

作者&#xff1a;来自 Elastic Matt Scourfield, Andrew Crothers, Brian Lambert 组织依靠知识产权 (IP) 来推动创新、保持竞争优势并创造收入来源。对于希望将新产品推向市场的公司来说&#xff0c;弄清楚谁拥有哪些专利是一项必不可少的能力。搜索数百万项专利可能既困难又耗…

协议栈攻击分类(CISP-PTE笔记)

CISP-PTE笔记 协议栈攻击分类 1.协议栈自身的脆弱性 ​ 1&#xff09;缺乏数据源验证机制 ​ 2&#xff09;缺乏完整性验证机制 ​ 3&#xff09;缺乏机密性验证机制 2.网络接口层攻击 3.网络层攻击 4.应用层攻击 网络攻击的基本模式 被动威胁&#xff08;不影响通信双…

SpringBoot3集成Junit5

目录 1. 确保项目中包含相关依赖2. 配置JUnit 53. 编写测试类4、Junit5 新增特性4.1 注解4.2 断言4.3 嵌套测试4.4 总结 在Spring Boot 3中集成JUnit 5的步骤相对简单。以下是你可以按照的步骤&#xff1a; 1. 确保项目中包含相关依赖 首先&#xff0c;确保你的pom.xml文件中…

智慧城市智慧城市项目方案-大数据平台建设技术方案(原件Word)

第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障…

算法练习:904. 水果成篮

题目链接&#xff1a;904. 水果成篮。 题目意思就是可以选取两个种类的水果不能超过两个种类&#xff0c;该种类个数没有限制&#xff0c; 但是一旦超过两个种类的水果就要停止计数。 示例中数组编号就是就是种类&#xff0c;就是不能出现三个不同编号的数。 1.暴力解法&…

wincc中全局脚本C(c语言脚本)的研究和解密

文章目录 前言一、分析 前言 很多时候我们在wincc中写全局脚本时会为自己的脚本添加密码&#xff0c;但很久很久以后再想修改密码忘记了怎么办呢。 一、分析 经过分析编码有了下面成功 ![请添加图片描述](https://i-blog.csdnimg.cn/direct/33baf91a49da410e82f16b4fbd746c48…

Java+控制台 商城销售系统

Java控制台 商城销售系统 一、系统介绍二、功能展示1.系统登陆2.二维数组实现商城销售系统3.类集实现商城销售系统 四、其它1.其他系统实现 一、系统介绍 实现一个xx商城销售系统的登录功能: 1)&#xff09;打开系统&#xff0c;给出欢迎信息。 2&#xff09;将用户名和密码定…

微服务中常用分布式锁原理及执行流程

1.什么是分布式锁 分布式锁是一种在分布式系统环境下实现的锁机制&#xff0c;它主要用于解决&#xff0c;多个分布式节点之间对共享资源的互斥访问问题&#xff0c;确保在分布式系统中&#xff0c;即使存在有多个不同节点上的进程或线程&#xff0c;同一时刻也只有一个节点可…

A15基于Spring Boot的宠物爱心组织管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

测试自动化如何和业务流程结合?

测试自动化框架固然重要&#xff0c;但是最终自动化的目的都是为了业务服务的。 那测试自动化如何对业务流程产生积极影响&#xff1f; 业务流程的重要性 测试自动化项目并非孤立存在&#xff0c;其生命周期与被测试的应用程序紧密相关。项目的价值在于被整个开发团队所使用&a…

AI - 使用LangChain请求LLM结构化生成内容

AI - 使用LangChain请求LLM结构化生成内容 基于深度学习的大型语言模型&#xff08;LLM&#xff09;不仅可以生成文本&#xff0c;还可以帮助我们完成许多复杂任务&#xff0c;如自动化客服、内容创作和数据分析。然而&#xff0c;如何从这些模型中结构化地获取输出&#xff0c…

使用GPT-SoVITS训练语音模型

1.项目演示 阅读单句话 1725352713141 读古诗 1725353700203 2.项目环境 开发环境&#xff1a;linux 机器配置如下&#xff1a;实际使用率百分之二十几&#xff0c; 3.开发步骤 1.首先是准备数据集&#xff0c;要求是wav格式&#xff0c;一到两个小时即可&#xff0c; 2.…

UML的另一个主角——用例图

顺序图和类图已经出过单集&#xff0c;本贴要分享的是用例图。 类图https://blog.csdn.net/jsl123x/article/details/143526286?spm1001.2014.3001.5501顺序图https://jslhyh32.blog.csdn.net/article/details/134350587 目录 一.系统 二.参与者 1.主要参与者 2.次要参与…

set和map的使用

目录 1.关联式容器 2.键值对 3.set 3.1set的模版参数列表 3.2对set的修改 3.2.1insert 3.2.2 erase 3.2.3clear 3.2.4swap 3.2.5 find 3.3set的迭代器 3.4set的容量 4.map 4.1对map的修改 4.1.1insert 4.1.2erase 4.1.3swap 4.1.4clear 4.2map的迭代器 4.3opera…

【5.10】指针算法-快慢指针将有序链表转二叉搜索树

一、题目 给定一个单链表&#xff0c;其中的 元素按升序排序 &#xff0c;将其转换为 高度平衡的二叉搜索树 。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。 示例: 给定的有序链表&#xff1a; [ -10 , -3 , 0 , …

dns服务器配置

主服务器 1.挂载点 mount /dev/sr0 /mnt 2.防火墙关闭 systemctl stop firewalld setenforce 0 3.下载bind软件 dnf install bind -y 4.进行正向解析配置 vim /etc/named.conf options { listen-on port 53 { 192.168.92.128; }; directo…

stable diffusion图生图

本节内容&#xff0c;给大家带来的是stable diffusion的图生图课程&#xff0c;我们在midjourney的课程中有学习过midjourney的图生图功能&#xff0c;即使用垫图的方式来引导AI绘制图片。图生图是AI绘图程序一个非常重要的功能&#xff0c;stable diffusion同样提供了类似的功…