Spring Security系列教程14--基于自定义的认证提供器实现图形验证码

简介: 前言在上一个章节中,一一哥 带大家实现了如何在Spring Security中添加执行自定义的过滤器,进而实现验证码校验功能。这种实现方式,只是实现验证码功能的方式之一,接下来我们再学习另一种实现方式,就是利用AuthenticationProvider来实现验证码功能,通过这个案例,我们学习如何进行自定义AuthenticationProvider。一. 认证提供器简介在上一章节中,我带各位利用自定义的过滤器实现了图形验证码效果,接下来我们利用另一种方式,基于自定义的认证提供器来实现图形验证码。1. 认证提供器AuthenticationProvider在第11章节中,壹哥 给大家

前言

在上一个章节中,一一哥 带大家实现了如何在Spring Security中添加执行自定义的过滤器,进而实现验证码校验功能。这种实现方式,只是实现验证码功能的方式之一,接下来我们再学习另一种实现方式,就是利用AuthenticationProvider来实现验证码功能,通过这个案例,我们学习如何进行自定义AuthenticationProvider。

一. 认证提供器简介

在上一章节中,我带各位利用自定义的过滤器实现了图形验证码效果,接下来我们利用另一种方式,基于自定义的认证提供器来实现图形验证码。

1. 认证提供器AuthenticationProvider

在第11章节中,壹哥 给大家讲过Spring Security的认证授权实现流程,其中就给大家讲解过AuthenticationProvider的作用,接下来我们看一下AuthenticationProvider接口的类关系图:

从上图中可知,AuthenticationProvider是一个接口,该接口有一个直接的子类AbstractUserDetailsAuthenticationProvider,该类有2个抽象的方法:additionalAuthenticationChecks() 和 retrieveUser(),如下图:

我们可以通过编写一个子类继承AbstractUserDetailsAuthenticationProvider,复写这2个抽象方法,进行满足自己需求的扩展实现。Spring Security中的DaoAuthenticationProvider子类就是通过复写这2个抽象方法,实现了基于数据库模型的认证授权。

我们今天会通过继承DaoAuthenticationProvider,来实现图形验证码的校验功能。

2. WebAuthenticationDetails类介绍

了解完上面的AuthenticationProvider类之后,我们还需要了解另一个类WebAuthenticationDetails。

我们知道在Spring Security中有一个UsernamePasswordAuthenticationToken类,封装了用户的principal、credentials信息,该类还从它的父类AbstractAuthenticationToken中继承了details信息。其中这个details信息表示认证用户的额外信息,比如请求用户的remoteAddress和sessionId等信息,这两个信息都是在另一个WebAuthenticationDetails类中定义的,所以我们可以利用WebAuthenticationDetails来封装用户的额外信息。

了解完上面的这些必要的API,我们就可以实现今天的需求了。

二. 实现图形验证码

1. 添加依赖包

我们还是和之前的案例一样,可以先创建一个新的module,创建过程略。

在本案例中我们依然采用github上的开源验证码解决方案kaptcha,所以需要在原有项目的基础上添加kaptcha的依赖包。

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>

2. 创建Producer对象

跟上一个案例一样,创建CaptchaConfig配置类,在该类中创建一个Producer对象,对验证码对象进行必要的配置。

@ConfigurationpublicclassCaptchaConfig {
@BeanpublicProducercaptcha() {
// 配置图形验证码的基本参数Propertiesproperties=newProperties();
// 图片宽度properties.setProperty("kaptcha.image.width", "150");
// 图片长度properties.setProperty("kaptcha.image.height", "50");
// 字符集properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
// 字符长度properties.setProperty("kaptcha.textproducer.char.length", "4");
Configconfig=newConfig(properties);
// 使用默认的图形验证码实现,当然也可以自定义实现DefaultKaptchadefaultKaptcha=newDefaultKaptcha();
defaultKaptcha.setConfig(config);
returndefaultKaptcha;
    }
}

3. 创建生成验证码的接口

在上面创建了Producer对象后,接着创建一个生成验证码的接口,该接口中负责生成验证码图片,并将验证码存储到session中。

@ControllerpublicclassCaptchaController {
@AutowiredprivateProducercaptchaProducer;
@GetMapping("/captcha.jpg")
publicvoidgetCaptcha(HttpServletRequestrequest, HttpServletResponseresponse) throwsIOException {
// 设置内容类型response.setContentType("image/jpeg");
// 创建验证码文本StringcapText=captchaProducer.createText();
// 将验证码文本设置到sessionrequest.getSession().setAttribute("captcha", capText);
// 创建验证码图片BufferedImagebi=captchaProducer.createImage(capText);
// 获取响应输出流ServletOutputStreamout=response.getOutputStream();
// 将图片验证码数据写到响应输出流ImageIO.write(bi, "jpg", out);
// 推送并关闭响应输出流try {
out.flush();
        } finally {
out.close();
        }
    }
}

4. 自定义异常

接下来自定义一个运行时异常,用于处理验证码校验失败时抛出异常提示信息。

publicclassVerificationCodeExceptionextendsAuthenticationException {
publicVerificationCodeException() {
super("图形验证码校验失败");
    }
}

5. 自定义WebAuthenticationDetails

我在上面给大家介绍过WebAuthenticationDetails这个类,知道该类中可以封装用户的额外信息,所以在这里我们自定义一个WebAuthenticationDetails类,封装验证码信息,并把用户传递过来的验证码与session中保存的验证码进行对比。

/*** 添加额外的用户认证信息*/publicclassMyWebAuthenticationDetailsextendsWebAuthenticationDetails {
privateStringimageCode;
privateStringsavedImageCode;
publicStringgetImageCode() {
returnimageCode;
    }
publicStringgetSavedImageCode() {
returnsavedImageCode;
    }
/*** 补充用户提交的验证码和session保存的验证码*/publicMyWebAuthenticationDetails(HttpServletRequestrequest) {
super(request);
this.imageCode=request.getParameter("captcha");
//获取session对象HttpSessionsession=request.getSession();
this.savedImageCode= (String) session.getAttribute("captcha");
if (!StringUtils.isEmpty(this.savedImageCode)) {
// 随手清除验证码,不管是失败还是成功,所以客户端应在登录失败时刷新验证码session.removeAttribute("captcha");
        }
    }
}

6. 自定义AuthenticationDetailsSource

AuthenticationDetailsSource是一个接口,该接口带有一个buildDetails方法,该方法会在创建一个新的authentication的details对象时被调用,而且可以在这里传递给details对象一个request参数,如下图所示:

所以这里我们定义一个AuthenticationDetailsSource类,通过该类构建出上面定义的WebAuthenticationDetails对象,并且给WebAuthenticationDetails传递进去HttpServletRequest对象。

@ComponentpublicclassMyWebAuthenticationDetailsSourceimplementsAuthenticationDetailsSource<HttpServletRequest,WebAuthenticationDetails> {
/*** 创建一个WebAuthenticationDetails对象*/@OverridepublicWebAuthenticationDetailsbuildDetails(HttpServletRequestrequest) {
returnnewMyWebAuthenticationDetails(request);
    }
}

7. 自定义DaoAuthenticationProvider

接下来通过继承DaoAuthenticationProvider父类,来引入对图形验证码的验证操作。

/*** 在常规的数据库认证之上,添加图形验证码功能*/@ComponentpublicclassMyAuthenticationProviderextendsDaoAuthenticationProvider {
/*** 构造方法注入UserDetailService和PasswordEncoder*/publicMyAuthenticationProvider(UserDetailsServiceuserDetailsService, PasswordEncoderpasswordEncoder) {
this.setUserDetailsService(userDetailsService);
this.setPasswordEncoder(passwordEncoder);
    }
/*** 在常规的认证之上,添加额外的图形验证码功能*/@OverrideprotectedvoidadditionalAuthenticationChecks(UserDetailsuserDetails, UsernamePasswordAuthenticationTokenusernamePasswordAuthenticationToken) throwsAuthenticationException {
//获取token令牌中关联的details对象,并将其转换为我们自定义的MyWebAuthenticationDetailsMyWebAuthenticationDetailsdetails= (MyWebAuthenticationDetails) usernamePasswordAuthenticationToken.getDetails();
StringimageCode=details.getImageCode();
StringsavedImageCode=details.getSavedImageCode();
// 检验图形验证码if (StringUtils.isEmpty(imageCode) ||StringUtils.isEmpty(savedImageCode) ||!imageCode.equals(savedImageCode)) {
thrownewVerificationCodeException();
        }
//在正常的认证检查之前,添加额外的关于图形验证码的校验super.additionalAuthenticationChecks(userDetails, usernamePasswordAuthenticationToken);
    }
}

8. 添加SecurityConfig

然后创建编写SecurityConfig类,关联配置我们前面编写的AuthenticationDetailsSource和AuthenticationProvider类。

@SuppressWarnings("all")
@EnableWebSecurity(debug=true)
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter {
@AutowiredprivateAuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>myWebAuthenticationDetailsSource;
@AutowiredprivateAuthenticationProviderauthenticationProvider;
@Overrideprotectedvoidconfigure(HttpSecurityhttp) throwsException {
http.authorizeRequests()
                .antMatchers("/admin/api/**")
                .hasRole("ADMIN")
                .antMatchers("/user/api/**")
                .hasRole("USER")
                .antMatchers("/app/api/**", "/captcha.jpg")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
//这里关联配置自定义的AuthenticationDetailsSource                .authenticationDetailsSource(myWebAuthenticationDetailsSource)
                .failureHandler(newSecurityAuthenticationFailureHandler())
                .successHandler(newSecurityAuthenticationSuccessHandler())
                .loginPage("/myLogin.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
//在这里关联我们自定义的AuthenticationProvider@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth) throwsException {
auth.authenticationProvider(authenticationProvider);
    }
@BeanpublicPasswordEncoderpasswordEncoder() {
returnNoOpPasswordEncoder.getInstance();
    }
}

9. 编写测试页面

最后编写一个自定义的登录页面,在这里添加对验证码接口的引用,我这里列出html的核心代码。

<body><divclass="login"><h2>Access Form</h2><divclass="login-top"><h1>登录验证</h1><formaction="/login"method="post"><inputtype="text"name="username"placeholder="username"/><inputtype="password"name="password"placeholder="password"/><divstyle="display: flex;"><!-- 新增图形验证码的输入框 --><inputtype="text"name="captcha"placeholder="captcha"/><!-- 图片指向图形验证码API --><imgsrc="/captcha.jpg"alt="captcha"height="50px"width="150px"style="margin-left: 20px;"></div><divclass="forgot"><ahref="#">忘记密码</a><inputtype="submit"value="登录"></div></form></div><divclass="login-bottom"><h3>新用户&nbsp;<ahref="#">&nbsp;</a></h3></div></div></body>

10. 代码结构

本案例的主要代码结构如下图所示,各位可以参考创建。

11. 启动项目测试

接下来我们启动项目,跳转到登录页面后,我们就可以看到验证码已经被创建出来了。

此时我们可以看到生成的数字验证码,在我们输入正确的用户名、密码、验证码后,就可以成功的登录进去访问web接口了。

至此,我们就实现了基于自定义的认证提供器来实现图形验证码功能了,这种实现方式要比第一种实现方式更复杂一些,其实都能满足我们的开发需求。有的小伙伴会问,开发时到底选择哪一种方式呢?壹哥觉得都无所谓的!你有什么更好的见解吗?可以在评论区留言哦!

相关文章
|
2月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
375 3
|
人工智能 Java Serverless
【MCP教程系列】搭建基于 Spring AI 的 SSE 模式 MCP 服务并自定义部署至阿里云百炼
本文详细介绍了如何基于Spring AI搭建支持SSE模式的MCP服务,并成功集成至阿里云百炼大模型平台。通过四个步骤实现从零到Agent的构建,包括项目创建、工具开发、服务测试与部署。文章还提供了具体代码示例和操作截图,帮助读者快速上手。最终,将自定义SSE MCP服务集成到百炼平台,完成智能体应用的创建与测试。适合希望了解SSE实时交互及大模型集成的开发者参考。
9669 60
|
18天前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
212 4
|
24天前
|
监控 安全 Java
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
Spring Boot 通过 Actuator 模块提供了强大的健康检查功能,帮助开发者快速了解应用程序的运行状态。默认健康检查可检测数据库连接、依赖服务、资源可用性等,但在实际应用中,业务需求和依赖关系各不相同,因此需要实现自定义健康检查来更精确地监控关键组件。本文介绍了如何使用 @HealthEndpoint 注解及实现 HealthIndicator 接口来扩展 Spring Boot 的健康检查功能,从而提升系统的可观测性与稳定性。
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
|
3月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
353 13
|
4月前
|
安全 Java 数据库
Spring Boot 框架深入学习示例教程详解
本教程深入讲解Spring Boot框架,先介绍其基础概念与优势,如自动配置、独立运行等。通过搭建项目、配置数据库等步骤展示技术方案,并结合RESTful API开发实例帮助学习。内容涵盖环境搭建、核心组件应用(Spring MVC、Spring Data JPA、Spring Security)及示例项目——在线书店系统,助你掌握Spring Boot开发全流程。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
490 2
|
缓存 安全 算法
Spring Security OAuth 2.0 资源服务器— JWT
Spring Security OAuth 2.0 资源服务器— JWT
1038 1
|
设计模式 安全 NoSQL
从零开始的Spring Security Oauth2(一)
从零开始的Spring Security Oauth2(一)
6826 3
从零开始的Spring Security Oauth2(一)