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接口了。

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

相关文章
|
1月前
|
安全 Java 数据库
安全无忧!在 Spring Boot 3.3 中轻松实现 TOTP 双因素认证
【10月更文挑战第8天】在现代应用程序开发中,安全性是一个不可忽视的重要环节。随着技术的发展,双因素认证(2FA)已经成为增强应用安全性的重要手段之一。本文将详细介绍如何在 Spring Boot 3.3 中实现基于时间的一次性密码(TOTP)双因素认证,让你的应用安全无忧。
84 5
|
2月前
|
数据采集 监控 安全
阿里云短信服务+图形认证,有效降低验证码盗刷概率
阿里云短信服务+图形认证服务,有效降低验证码盗刷概率。
248 3
阿里云短信服务+图形认证,有效降低验证码盗刷概率
|
27天前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
65 2
|
2月前
|
XML JavaScript Java
Spring Retry 教程
Spring Retry 是 Spring 提供的用于处理方法重试的库,通过 AOP 提供声明式重试机制,不侵入业务逻辑代码。主要步骤包括:添加依赖、启用重试机制、设置重试策略(如异常类型、重试次数、延迟策略等),并可定义重试失败后的回调方法。适用于因瞬时故障导致的操作失败场景。
Spring Retry 教程
|
1月前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
详细介绍实现Java Spring Boot FCM推送教程
96 0
|
2月前
|
存储 NoSQL Java
|
3月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
3月前
|
Java Spring 安全
Spring 框架邂逅 OAuth2:解锁现代应用安全认证的秘密武器,你准备好迎接变革了吗?
【8月更文挑战第31天】现代化应用的安全性至关重要,OAuth2 作为实现认证和授权的标准协议之一,被广泛采用。Spring 框架通过 Spring Security 提供了强大的 OAuth2 支持,简化了集成过程。本文将通过问答形式详细介绍如何在 Spring 应用中集成 OAuth2,包括 OAuth2 的基本概念、集成步骤及资源服务器保护方法。首先,需要在项目中添加 `spring-security-oauth2-client` 和 `spring-security-oauth2-resource-server` 依赖。
53 0
|
3月前
|
SQL Java 数据库连接
Spring Boot联手MyBatis,打造开发利器:从入门到精通,实战教程带你飞越编程高峰!
【8月更文挑战第29天】Spring Boot与MyBatis分别是Java快速开发和持久层框架的优秀代表。本文通过整合Spring Boot与MyBatis,展示了如何在项目中添加相关依赖、配置数据源及MyBatis,并通过实战示例介绍了实体类、Mapper接口及Controller的创建过程。通过本文,你将学会如何利用这两款工具提高开发效率,实现数据的增删查改等复杂操作,为实际项目开发提供有力支持。
184 0
|
3月前
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
185 0