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

2023-12-14 11:12

本文主要是介绍【个人版】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的构建,不过第一种方案灵活性更高,我们自己组织和封装的可能性更好,体现模块化的思想,适配方式已经标注过期,最新版本已经删除,可作参考。

这篇关于【个人版】SpringBoot下Spring-Security自定义落地篇【三】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.