【个人版】SpringBoot下Spring-Security自定义落地篇【三】

背景: 前两篇文章将spring-security的设计架构、核心类、配置及构建过程基本过了一遍,其实很偏理论,如果对源码不感兴趣或项目使用不深,基本可以忽略,毕竟完全理解可能也不会用到,时间长也忘掉了。但是如果你想对代码进行微调,或者写出自己想要的设计效果,那么读一读还是很有必要,毕竟开发过程就是一个学习的过程。本篇是在参考遍地继承WebSecurityConfigurerAdapter的方案上,再加上自身阅读源码后的理解,结合自身需求而尝试出来的。

Spring-Security全局导读:
1、Security核心类设计
2、HttpSecurity结构和执行流程解读
3、Spring-Security个人落地篇

ps1:WebSecurityConfigurerAdapter在较新的版本中都已经被标注过期了,这也是我读源码时尝试自己尝试新方案的动机之一
ps2: 落地方案其实和最新官方推荐的方案很接近,因为我也是从自动配置类中的SecurityFilterChain的创建过程而猜想过来的
ps3:强烈建议阅读松哥之前写的security系列文章,看看这一篇就知道他的水平了
ps4:如果有时间&有想法,自己写一些试试,很久以前粗略看过松哥Security系列的一部分文章,当时觉得自己懂了,不知过了多久,已经完全忘记基础概念了。

废话过多,以下为个人输出(简略篇):
一、POM依赖:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://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>2.7.14</version><relativePath/> </parent><groupId></groupId><artifactId></artifactId><version></version><name></name><description></description><properties><java.version></java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>compile</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

二、自定义拦截及授权封装类

/*** 1、CustomAuthenticationProvider等其它内部类不能设置为静态类* 2、CustomUsernamePasswordAuthenticationFilter必须重写afterPropertiesSet方法并忽略管理器校验*/
@Component
@ConditionalOnProperty(value = "password.enable", havingValue = "true", matchIfMissing = true)
public class CustomAuthenticationContext {@Componentprivate class CustomAuthenticationProvider implements AuthenticationProvider {@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;if ("test".equals(token.getPrincipal().toString()) && "test".equals(token.getCredentials().toString())) {return UsernamePasswordAuthenticationToken.authenticated("test", null, null);}throw new BadCredentialsException("账号信息错误");}@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);}}@Componentprivate class CustomUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();private ObjectMapper objectMapper;public CustomUsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/user/login", "POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {InputStream content = request.getInputStream();User user = objectMapper.readValue(content, User.class);Set<ConstraintViolation<User>> validate = validator.validate(user, Default.class);if (validate.isEmpty()) {UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(user.getName(), user.getPassword());authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));return this.getAuthenticationManager().authenticate(authRequest);}throw new BadCredentialsException("用户名或密码错误");}@Autowiredvoid init(CustomAuthResult authResult, ObjectMapper objectMapper) {this.objectMapper = objectMapper;setAuthenticationSuccessHandler(authResult);setAuthenticationFailureHandler(authResult);}@Override// 后期统一设置,未重写则实例创建后校验报错public void afterPropertiesSet() {}}@Dataprivate static class User {@NotBlankprivate String name;@NotBlankprivate String password;}	}

说明:
1、自定义鉴权中,Filter及AuthenticationProvider通常是一对一的,此处封装为一个类,并用条件注解修饰,避免多场景下的鉴权组合造成的代码混乱(虽然此类场景通常不会有太大的变更)
2、此块逻辑其实可以放到controller中,但注意授权上下文的控制及HttpSecurity的设置

三、Security框架配置入口类

@Configuration
public class SecurityFilterChainConfiguration {@Autowired// HttpSecurity默认是多例的,此处如果多个方法调用,必须唯一private HttpSecurity httpSecurity;// 参数列表可通过自定义类上条件注解控制// 单个类中同时存在多个@Autowired,执行顺序不固定// 这两个类是一体的,可以封装在一起,不用搞得太零散@Autowiredvoid authenticationManager(List<AuthenticationProvider> customProviders, List<AbstractAuthenticationProcessingFilter> customProcessingFilter) {// 自封装授权管理器,没有使用系统AuthenticationManagerBuilder创建的管理器AuthenticationManager authenticationManager = new ProviderManager(customProviders);for (AbstractAuthenticationProcessingFilter filter : customProcessingFilter) {filter.setAuthenticationManager(authenticationManager);// UsernamePasswordAuthenticationFilter.class为系统内置FilterhttpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);}}@BeanSecurityFilterChain securityFilterChain(CustomAuthResult authResult) throws Exception {/** 此块HttpSecurity链式配置包含三块内容* 1、csrf会在response中添加token header,以避免非原始请求客户端伪造访问* 2、关闭默认的表单登录,所有鉴权逻辑自定义* 3、针对logout、异常等场景细节进行配置*/httpSecurity.csrf().disable()// 如果自定义授权了,表单登录可以关闭,通常用于前后台分离项目.formLogin().disable().authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()// 这块是为在controller中做授权校验而放开的,此场景后续补充.antMatchers(HttpMethod.POST, "/doLogin").permitAll().anyRequest().authenticated().and().logout().logoutSuccessHandler(authResult).permitAll()// 设置访问未授权资源的处理器.and().exceptionHandling().authenticationEntryPoint(authResult);// 手工build,没有借助WebSecurity类的管理return httpSecurity.build();}
}

四、响应封装合体类

@Component
public class CustomAuthResult implements AuthenticationFailureHandler, AuthenticationSuccessHandler, AuthenticationEntryPoint, LogoutSuccessHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("{\"code\":\"1004\",\"msg\":\"用户名或密码错误\"}");response.getWriter().flush();response.getWriter().close();}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");response.getWriter().write("{\"code\":\"1000\",\"msg\":\"登录成功\"}");response.getWriter().flush();response.getWriter().close();}@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");response.setStatus(HttpStatus.FORBIDDEN.value());response.getWriter().write("{\"code\":\"1001\",\"msg\":\"用户未登录\"}");response.getWriter().flush();response.getWriter().close();}@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8");response.getWriter().write("{\"code\":\"1003\",\"msg\":\"成功退出\"}");response.getWriter().flush();response.getWriter().close();}
}

说明:此类自己调试随便建的,生产项目需要有严格而准确的封装和处理

简单自定义使用,以上就足够了,相关注意事项也在注释中说明,不管是继承WebSecurityConfigurerAdapter的方式,还是其他方式,核心类的执行流程其实是不变的,变的只是配置方式或组合方式的外皮,所以把基础概念了解清楚了,不管是spring5还是spring6的版本变化,基本不会有太大的挑战。

PS:
1、关于用户权限数据管理逻辑,框架层提供了UserDetailsService接口及对应内存&数据库的默认实现,这个需求差异较大,如验证码登录,此处不再展开,直接在验证逻辑处写死配置。
2、在新版本中(参考依赖),不管是继承WebSecurityConfigurerAdapter的方式,还是哪种方式,都不需要再自行使用@EnableWebSecurity注解在类上进行标注了,自动配置类中已告知。

附Adapter模式简化配置版:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowired// 代码参考上述方案定义private CustomAuthResult authResult;@Autowired// AuthenticationManager配置类,复用全局AuthenticationManagerprivate AuthenticationConfiguration configuration;@Override// 适配器模式只需要重写此方法,完成内部过滤器链配置protected void configure(HttpSecurity http) throws Exception {// 自定义安全管理过滤器,对应鉴权和参数封装逻辑此处忽略http.addFilterBefore(adaptUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);http.csrf().disable().formLogin().disable().authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and().logout().logoutSuccessHandler(authResult).permitAll().and().exceptionHandling().authenticationEntryPoint(authResult);}@Bean// 自定义Filter内部有其他Autowired注解需要处理,所以需要发布到容器// 如果手工set,可以考虑不发布到spring容器中,限制其作用域范围AdaptUsernamePasswordAuthenticationFilter adaptUsernamePasswordAuthenticationFilter() throws Exception {AuthenticationManager authenticationManager = configuration.getAuthenticationManager();AdaptUsernamePasswordAuthenticationFilter adaptUsernamePasswordAuthenticationFilter = new AdaptUsernamePasswordAuthenticationFilter("/user/login");adaptUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager);return adaptUsernamePasswordAuthenticationFilter;}@Bean// 自定义授权管理器,代码和上述方案定义类一致,但上述是内部类,包路径不一致public AuthenticationProvider authenticationProvider() {return new CustomAuthenticationProvider();}
}

可以看到继承WebSecurity适配器的代码同样也很简单,核心还是HttpSecurity的构建,不过第一种方案灵活性更高,我们自己组织和封装的可能性更好,体现模块化的思想,适配方式已经标注过期,最新版本已经删除,可作参考。

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

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

相关文章

C语言->动态内存管理

系列文章目录 文章目录 前言 ✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青_C语言,函数,指针-CSDN博客 目的&#xff1a;学习malloc&#xff0c…

动手学深度学习-注意力机制

10.1注意力提示 自主性注意力机制 有意识的注意力机制。非自主性注意力机制 无意识的注意力机制。 小结: 人类的注意力是有限的&#xff0c;有价值和稀缺的资源。受试者使用非自主性和自主性提示有选择的引导注意力&#xff0c;前者基于突出性&#xff0c;后者则依赖于意识。…

Android多国语言翻译 国际化

语言目录详细对应关系 Arabic, Egypt (ar-rEG) —————————–阿拉伯语&#xff0c;埃及 Arabic, Israel (ar-rIL) ——————————-阿拉伯语&#xff0c;以色列 Bulgarian, Bulgaria (bg-rBG) ———————保加利亚语&#xff0c;保加利亚 Catalan, Spain (ca-r…

24年五年制专转本招生院校有可能发生变动

据悉&#xff0c;24年五年制专转本院校可能将发生改变&#xff0c;南京传媒学院有可能停止招生 将新增一所招生大学&#xff1a;南京航空航天大学金城学院 南京航空航天大学金城学院始建于1999年&#xff0c;是南京航空航天大学联合社会力量创办的独立学院。学校位于江苏省南京…

【Python百宝箱】挑战网络分析:NetworkX、iGraph、Graph-tool、Snap.py 和 PyGraphviz详细评测

五大 Python 网络分析工具库大揭秘&#xff1a;功能、性能对比 前言 随着信息时代的来临&#xff0c;网络结构的分析变得日益重要。在 Python 生态系统中&#xff0c;有许多强大的库可用于网络分析&#xff0c;如 NetworkX、iGraph、Graph-tool、Snap.py 和 PyGraphviz。这五…

Selenium IED-安装及简单使用

本文已收录于专栏 《自动化测试》 目录 背景介绍优势特点安装步骤录制脚本总结提升 背景介绍 Selenium 通过使用 WebDriver 支持市场上所有主流浏览器的自动化。 Webdriver 是一个 API 和协议&#xff0c;它定义了一个语言中立的接口&#xff0c;用于控制 web 浏览器的行为。 每…

WPF中DataGrid设置默认选中行

1、DataGrid命名为planDataGrid <DataGrid ItemsSource"{Binding PlanList}" SelectedItem"{Binding SelectedItem}" x:Name"planDataGrid" AutoGenerateColumns"False" CanUserAddRows"False" GridLinesVisib…

luttuce(RedisTempate)实现hash expire lua脚本

话不多说先放脚本&#xff1a; local argv ARGV local length #argv if length > 0 then local unpackArgs {} for i 1, length - 1 dotable.insert(unpackArgs, argv[i]) end if redis.call(exists, KEYS[1]) 1 thenredis.call(del, KEYS[1])redis.call(hset, KEYS[…

成都工业学院Web技术基础(WEB)实验二:HTML5表格、表单标签的使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

报表生成器Stimulsoft用户手册:预览中具有动态数据排序的报告

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

快来看!苹果开放侧载,对开发者来说是祸是福?

不知道你们听说了没有&#xff1f; 苹果公司在向SEC提供的2023年10-K文件中明确表现&#xff0c;伴随着欧盟委员会《数字市场法案》的正式落地将不得不在苹果手机上开放“应用侧载”功能。 简单来说&#xff0c;就是你的App可以不用在App Store里下载&#xff0c;而是可以通过…

【深度学习目标检测】六、基于深度学习的路标识别(python,目标检测,yolov8)

YOLOv8是一种物体检测算法&#xff0c;是YOLO系列算法的最新版本。 YOLO&#xff08;You Only Look Once&#xff09;是一种实时物体检测算法&#xff0c;其优势在于快速且准确的检测结果。YOLOv8在之前的版本基础上进行了一系列改进和优化&#xff0c;提高了检测速度和准确性。…

SQL Server数据库使用T-SQL语句简单填充

文章目录 操作步骤&#xff1a;1.新建数据库起名RGB2.新建表起名rgb3.添加三个列名4.点击新建查询5.填入以下T-SQL语句&#xff0c;点击执行&#xff08;F5&#xff09;6.刷新之后&#xff0c;查看数据 操作环境&#xff1a; win10 Microsoft SQL Server Management Studio 20…

vcpkg下载及安装

文章目录 vcpkg是什么vcpkg的优势Windows环境下的下载及安装1.下载 Linux环境下的下载及安装常用命令介绍1.1.1 设置默认安装的平台1.1.2可选步骤&#xff0c;将vcpkg与Visual Studio配合使用&#xff08;需要管理员权限&#xff09;1.1.3 软件包升级1.1.4 查找安装软件包1.1.5…

vm虚拟机操作

当时第一次对着敲对指令理解不完全&#xff0c;总体感觉脑子很乱&#xff0c;所以这里整理一下 ftp的搭建操作 [useracentos79 yum.repos.d]$ sudo -i [sudo] usera 的密码&#xff1a; // 输入的密码不显示 Pwd123.com [rootcentos79 ~] mv /etc/yum.repos.d/Cen…

LLM(六)| Gemini:谷歌Gemini Pro 开放API ,Gemini Pro 可免费使用

近期&#xff0c;Google Gemini Pro 开放API 了&#xff0c;且Gemini Pro 可免费使用&#xff01;Gemini Pro支持全球180个国家的38种语言&#xff0c;目前接受文本作为输入并生成文本作为输出。 Gemini API 地址&#xff1a;http://ai.google.dev Gemini Pro 的表现超越了其他…

全栈开发组合

SpringBoot是什么&#xff1f; SpringBoot是一个基于Spring框架的开源框架&#xff0c;由Pivotal团队开发。它的设计目的是用来简化Spring应用的初始搭建以及开发过程。SpringBoot提供了丰富的Spring模块化支持&#xff0c;可以帮助开发者更轻松快捷地构建出企业级应用 Sprin…

python基本数据类型(二)-数

数 在编程中&#xff0c;经常使用数来记录得分、表示可视化数据、存储Web应用信息&#xff0c;等等。Python能根据数的用法以不同的方式处理它们。 1.整数 在Python中&#xff0c;可对整数执行加&#xff08;&#xff09;减&#xff08;-&#xff09;乘&#xff08;*&#xf…

JavaScript——基本数据类型和运算符

数字&#xff08;number 包含NaN&#xff09; 整数浮点数负数科学计数法Infininfty&#xff08;无限大&#xff09; 字符串 字符串可以用双引号&#xff08; " " &#xff09;、单引号&#xff08; ’ ’ &#xff09;或反引号&#xff08;&#xff09;三种形式 …

抖音直播互动答题问答猜图猜成语图汉字找茬找不同微信字节流量主小程序开发

抖音直播互动答题问答猜图猜成语图汉字找茬找不同微信字节流量主小程序开发 抖音直播互动答题&#xff1a;在抖音直播中&#xff0c;主播可以进行答题活动&#xff0c;观众可以通过答题参与互动。主播会提出问题&#xff0c;观众在规定时间内发送答案&#xff0c;主播根据正确率…