spring boot 3.x版本中集成spring security 6.x版本进行实现动态权限控制解决方案

一、背景

最近在进行项目从jdk8spring boot 2.7.x版本技术架构向jdk17spring boot 3.3.x版本的代码迁移,在迁移过程中,发现spring boot 3.3.x版本依赖的spring security版本已经升级6.x版本了,语法上和spring security 5.x版本有很多地方不兼容,因此记录试一下spring boot 3.3.x版本下,spring security 6.x的集成方案。

二、技术实现

1. 创建spring boot 3.3.x版本项目

spring boot 3.3.x版本对jdk版本要求较高,我这里使用的是jdk17,不久前,jdk21也已经发布了,可以支持虚拟线程,大家也可以使用jdk21

设置好jdk版本以后,新建项目,导入项目需要的相关依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.1</version></parent><groupId>com.j.ss</groupId><artifactId>spring-secrity6-spring-boot3-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>spring-secrity6-spring-boot3-demo</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>

2. 创建两个测试接口

  • 创建两个接口用于测试,源码参考如下

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class SecurityController {@GetMapping("/hello")public String hello() {return "hello, spring security.";}@PostMapping("/work")public String work() {return "I am working.";}}
    
  • 启动项目,测试一下接口是否正常

    • hello接口

    • work接口
      在这里插入图片描述

3. 引入spring-boot-starter-security依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

引入spring-boot-starter-security依赖以后,此时访问接口,会有未授权问题。

在这里插入图片描述

4. 定义UserDetailsManager实现类

spring security框架会自动使用UserDetailsManagerloadUserByUsername方法进行用户加载,在加载用户以后,会在UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中,进行前端输入的用户信息和加载的用户信息进行信息对比。

import lombok.extern.java.Log;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;@Component
@Log
public class MyUserDetailsManager implements UserDetailsManager {@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {/*** 这里为了演示方便,模拟从数据库查询,直接设置一下权限*/log.info("query user from db!");return queryFromDB(username);}private static UserDetails queryFromDB(String username) {GrantedAuthority authority = new SimpleGrantedAuthority("testRole");List<GrantedAuthority> list = new ArrayList<>();list.add(authority);return new User("jack", // 用户名称new BCryptPasswordEncoder().encode("123456"), //密码list      //权限列表);}
}

5. 定义权限不足处理逻辑

用户在访问没有权限的接口时,会抛出异常,spring security允许我们自己这里这种异常,我这里就是模拟一下权限不足的提示信息,不做过多处理。

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.io.PrintWriter;@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {//登陆状态下,权限不足执行该方法response.setStatus(200);response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter printWriter = response.getWriter();String body = "403,权限不足!";printWriter.write(body);printWriter.flush();}
}

6. 定义未登录情况处理逻辑

当用户没有登录情况下,访问需要权限的接口时,会抛出异常,spring security允许我们自定义处理逻辑,这里未登录就直接抛出401,提示用户登录。

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {//验证为未登陆状态会进入此方法,认证错误response.setStatus(401);response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter printWriter = response.getWriter();String body = "401, 请先进行登录!";printWriter.write(body);printWriter.flush();}
}

7. 定义自定义动态权限检验处理逻辑

在请求接口进行安全访问的时候,我们可以指定访问接口需要的角色,但是实际应用中,为了满足系统的灵活性,我们往往需要自定义动态权限的校验逻辑。

import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;import java.util.Collection;
import java.util.function.Supplier;@Component
public class MyAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {/*** @param authentication the {@link Supplier} of the {@link Authentication} to check* @param object         the {@link T} object to check* @return*/@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {// 获取访问urlString requestURI = object.getRequest().getRequestURI();// 模拟从数据库或者缓存里面查询拥有当前URI的权限的角色String[] allRole = query(requestURI);// 获取当前用户权限Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();// 判断是否拥有权限for (String role : allRole) {for (GrantedAuthority r : authorities) {if (role.equals(r.getAuthority())) {return new AuthorizationDecision(true); // 返回有权限}}}return new AuthorizationDecision(false); //返回没有权限}/*** 查询当前拥有对应url的权限的角色** @param requestURI* @return*/private String[] query(String requestURI) {return new String[]{"testRole"};}
}

8. 定义安全访问统一入口

在统一入口,我们可以做一些统一的逻辑,比如前后端分离的情况下,进行token内容的解析,这里我只是用代码模拟演示一下,方便大家理解。

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.java.Log;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;@Component
@Log
public class MyAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token"); // 前后端分离的时候获取tokenif (StringUtils.hasText(token)) { // 如果token不为空,则需要解析出用户信息,填充到当前上下文中UsernamePasswordAuthenticationToken authentication = getUserFromToken(token);SecurityContextHolder.getContext().setAuthentication(authentication);if (log.isLoggable(Level.INFO)) {log.info("set authentication");}} else {if (log.isLoggable(Level.INFO)) {log.info("user info is null.");}}filterChain.doFilter(request, response);}private UsernamePasswordAuthenticationToken getUserFromToken(String token) {GrantedAuthority authority = new SimpleGrantedAuthority(token);List<GrantedAuthority> list = new ArrayList<>();list.add(authority);User user = new User("jack", // 用户名称new BCryptPasswordEncoder().encode("123456"), //密码list      //权限列表);UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());usernamePasswordAuthenticationToken.setDetails(user);return usernamePasswordAuthenticationToken;}
}

9. 编写spring security配置类

当所有准备工作,做好以后,下面就是编写spring security的配置类了,使我们的相关配置生效。

import com.j.ss.MyAccessDeniedHandler;
import com.j.ss.MyAuthenticationEntryPoint;
import com.j.ss.MyAuthenticationFilter;
import com.j.ss.MyAuthorizationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** @Configuration 注解表示将该类以配置类的方式注册到spring容器中*/
@Configuration
/*** @EnableWebSecurity 注解表示启动spring security*/
@EnableWebSecurity
/*** @EnableMethodSecurity 注解表示启动全局函数权限*/
@EnableMethodSecurity
public class WebSecurityConfig {/*** 权限不足处理逻辑*/@Autowiredprivate MyAccessDeniedHandler accessDeniedHandler;/*** 未授权处理逻辑*/@Autowiredprivate MyAuthenticationEntryPoint authenticationEntryPoint;/*** 访问统一处理器*/@Autowiredprivate MyAuthenticationFilter authenticationTokenFilter;/*** 自定义权限校验逻辑*/@Autowiredprivate MyAuthorizationManager myAuthorizationManager;/*** spring security的核心过滤器链** @param httpSecurity* @return*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {// 定义安全请求拦截规则httpSecurity.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {authorizationManagerRequestMatcherRegistry.requestMatchers("/hello").permitAll() // hello 接口放行,不进行权限校验.anyRequest()// .hasRole() 其他接口不进行role具体校验,进行动态权限校验.access(myAuthorizationManager); // 动态权限校验逻辑})// 前后端分离,关闭csrf.csrf(AbstractHttpConfigurer::disable)// 前后端分离架构禁用session.sessionManagement(httpSecuritySessionManagementConfigurer -> {httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS);})// 访问异常处理.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {httpSecurityExceptionHandlingConfigurer.accessDeniedHandler(accessDeniedHandler);})// 未授权异常处理.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(authenticationEntryPoint);}).headers(httpSecurityHeadersConfigurer -> {// 禁用缓存httpSecurityHeadersConfigurer.cacheControl(HeadersConfigurer.CacheControlConfig::disable);httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable);});// 添加入口filter, 前后端分离的时候,可以进行token解析操作httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();}/*** 明文密码加密** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 忽略权限校验** @return*/@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web -> web.ignoring().requestMatchers("/hello"));}}

三、 功能测试

上述代码编写完成以后,启动项目,下面进行功能测试。

1. 忽略权限校验测试

访问/hello接口

在这里插入图片描述

可以看到,此时接口在无登录信息的情况下,也可以正常访问的。

2. 无权限测试

同样的,我们直接访问/work接口

在这里插入图片描述

可以看到,此时提醒我们需要登录了。

3. 有权限测试

再次访问/work接口,模拟已经登录,并拥有对应的权限。

在这里插入图片描述

可以看到,我们模拟有testRole权限,此时访问是正常的。

4. 权限不足测试

再次访问/work接口,模拟已经登录,但拥有错误的权限。

在这里插入图片描述

可以看到,此时报出了权限不足的异常。

四、写在最后

上面的案例只是演示,spring security的实际应用,应该根据具体项目权限要求来进行合理实现。

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

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

相关文章

Mysql中存储引擎简介、修改、查询、选择

场景 数据库存储引擎 数据库存储引擎是数据库底层软件组件&#xff0c;数据库管理系统&#xff08;DBMS &#xff09;使用数据引擎进行创建、查询、更新和删除数据的操作。 不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能&#xff0c;使用不同的存储引擎还可以…

【C++报错已解决】Invalid Use of ‘this’ Pointer

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言 一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;修正‘this’指针使用2…

React+TS前台项目实战(二十六)-- 高性能可配置Echarts图表组件封装

文章目录 前言CommonChart组件1. 功能分析2. 代码详细注释3. 使用到的全局hook代码4. 使用方式5. 效果展示 总结 前言 Echarts图表在项目中经常用到&#xff0c;然而&#xff0c;重复编写初始化&#xff0c;更新&#xff0c;以及清除实例等动作对于开发人员来说是一种浪费时间…

LVS-DR负载均衡

LVS-DR负载均衡 LVS—DR工作模式 原理 客户端访问调度器的VIP地址&#xff0c;在路由器上应该设置VIP跟调度器的一对一的映射关系&#xff0c;调度器根据调度算法将该请求“调度“到后端真实服务器&#xff0c;真实服务器处理完毕后直接将处理后的应答报文发送给路由器&#xf…

EDI安全:如何在2024年保护您的数据免受安全和隐私威胁

电子数据交换&#xff08;EDI&#xff09;支持使用标准化格式在组织之间自动交换业务文档。这种数字化转型彻底改变了业务通信&#xff0c;消除了对纸质交易的需求并加速了交易。然而&#xff0c;随着越来越依赖 EDI 来传输发票、采购订单和发货通知等敏感数据&#xff0c;EDI …

【跨境分享】中国商家如何卷到国外?电商独立站和电商平台的优势对比

为什么要选择独立站而不是电商平台 对于跨境电商经营者而言&#xff0c;采取多平台、多站点的运营策略是至关重要的战略布局。这一做法不仅有助于分散风险&#xff0c;避免将所有投资集中于单一市场&#xff0c;从而降低“所有鸡蛋置于同一篮子”的隐患&#xff0c;而且有利于拓…

【友邦保险-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

华为od相关信息分享

2024年OD统一考试&#xff08;D卷&#xff09;完整题库&#xff1a;华为OD机试2024年最新题库&#xff08;Python、JAVA、C合集&#xff09; 问 1.什么是华为od&#xff1f; 答&#xff1a;OD全称是Outsourcing Dispacth&#xff0c;即外包派遣&#xff0c;是华为和外企德科…

Kafka日志处理:深入了解偏移量查找与切分文件

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hello, 大家好!我是你们的技术小伙伴小米,今天要和大家分享一些关于Kafka日志处理的深入知识。我们将讨论如何查看偏移量为23的消息,以及Kafka日志分…

重载、覆盖(重写)、重定义(同名隐藏)的区别 (超详解)

&#x1f4da; 重载&#xff08;Overloading&#xff09;、覆盖&#xff08;Overriding&#xff09;、重定义&#xff08;Hiding&#xff09;是面向对象编程中常见的概念&#xff0c;它们分别用于描述不同情况下函数或方法的行为。 目录 重载&#xff08;Overloading&#xff…

ST7789 linux4.x驱动

文章目录 ST7789 linux4.x驱动设备树配置驱动程序编译驱动测试驱动 ST7789 linux4.x驱动 设备树配置 pinctrl_ecspi2_cs_1: ecspi2_cs_grp-1 {fsl,pins <MX6UL_PAD_CSI_DATA01__GPIO4_IO22 0x40017059>; };pinctrl_ecspi2_1: escpi2grp {fsl,pins <MX6UL_PAD_CSI_…

RocketMQ快速入门:主从、集群模式原理及搭建实操(十一)

目录 0. 引言1. 前备知识1.1 namesrv集群模式1.2 broker集群模式1.2 broker主从复制原理 2. 集群部署2.1 环境准备2.2 配置讲解2.3 一主多从模式部署2.4 多主无从模式部署2.5 多主多从模式部署 3. 总结 0. 引言 在学习完rocketmq的基础知识后&#xff0c;我们进入rocketmq高可…

和Bug较劲的第n天:[Error: Unable to open snapshot file: No such file or directory]

问题描述 最近做了一个小demo&#xff0c;基于parcel的&#xff0c;在迁移仓库的时候发生了一个报错 [Error: Unable to open snapshot file: No such file or directory] 原因分析&#xff1a; 在迁移仓库的时候&#xff0c;我将项目放入了一个以中文命名的文件夹里&#xf…

模电基础 - 信号的运算和处理

目录 一. 简介 二. 加法 三. 减法 四. 乘法 五. 除法 六. 总结 一. 简介 在模电基础中&#xff0c;信号的运算和处理是非常重要的内容。 信号的运算包括加法、减法、乘法、除法等。通过使用集成运放&#xff0c;可以很容易地实现这些运算。例如&#xff0c;利用反相输入…

算法的几种常见形式

算法&#xff08;Algorithm&#xff09; 算法&#xff08;Algorithm&#xff09;是指解决问题或完成任务的一系列明确的步骤或规则。在计算机科学中&#xff0c;算法是程序的核心部分&#xff0c;它定义了如何执行特定的任务或解决特定的问题。算法可以用多种方式来表示和实现…

宜春旅游集散中心展厅OLED透明屏方案设计

一、项目概述 为提升宜春旅游集散中心展厅的现代化展示水平&#xff0c;增强游客的参观体验&#xff0c;我们计划在展厅的核心区域引入OLED透明屏技术。该方案旨在通过高科技的视觉呈现方式&#xff0c;将展品信息以虚拟与现实相结合的方式展现&#xff0c;打造出一个既具科技感…

谷粒商城学习笔记-22-分布式组件-SpringCloud-OpenFeign测试远程调用

文章目录 一&#xff0c;OpenFeign的简介二&#xff0c;OpenFeign的使用步骤1&#xff0c;场景说明2&#xff0c;引入依赖2&#xff0c;开启OpenFeign3&#xff0c;编写Feign接口4&#xff0c;使用feign调用远程接口5&#xff0c;验证 错误记录 上一节学习了注册中心&#xff0…

鼠标录制工具|键鼠轨迹录制,实现自动办公

利用键鼠录制工具录制固定的鼠标点击、键盘输入等操作&#xff0c;实现自动化执行固定操作&#xff0c;节省时间。鼠标录制功能可以录制多步骤的操作&#xff0c;将录制的动作保存并命名&#xff0c;甚至可以编辑操作速度。下面将演示几种生活中常见的案例&#xff0c;详细讲解…

企业微信hook接口协议,移除群成员通知

移除群成员通知 返回示例 {"flag": 0, "receiver": 0, "sender_name": "", "is_room": 1, "server_id": 15318083, "send_time": 1687688952, "sender": 1688855749266556, "referid&…

插件更新了!

最近花了点时间&#xff0c;给网页插件添加了新功能&#xff0c;下面简单给大家介绍一下如何使用 我们安装好插件后&#xff0c;进入网页就可以看到一个带logo的按钮了&#xff0c;我们可以点一下就可以跳出快捷操作 不同页面点击会出现不同的功能&#xff0c;大家可以根据自己…