自定义认证器:实现验证码功能
944字约3分钟
2024-08-08
思路分析
Security 默认的有一个 DaoAuthenticationProvider 认证处理器,但只支持用户名和密码方式登录,所以需要我们自己实现一个认证器,来覆盖这个默认的认证器,思路如下:
1、创建一个认证器,继承默认的密码认证器 DaoAuthenticationProvider
2、定义验证码认证器的逻辑
从本地缓存中获取保存的验证码
从请求参数中获取用户输入的验证码
比对验证码
如果匹配成功,则调用DaoAuthenticationProvider的authenticate方法,进行原先逻辑认证
如果匹配失败,则抛出异常,不走后面的逻辑
3、将自定义的provider加到AuthenticationManager中
大致思路是这样,当然我们还可以通过过滤器实现,但是这个过滤器的优先级要先于认证过滤器之前,这里我们先通过自定义认证器来实现验证码校验的功能
代码实现
项目整体结构图

pom.xml
<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>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
KaptchaConfig(验证码配置类)
/**
* @ClassName KaptchaConfig
* @Desciption 验证码配置类
* @Author MaRui
* @Date 2023/9/19 14:22
* @Version 1.0
*/
@Configuration
public class KaptchaConfig {
@Bean
public Producer producer() {
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "150");
properties.setProperty("kaptcha.image.height", "50");
properties.setProperty("kaptcha.textproducer.char.string", "012");
properties.setProperty("kaptcha.textproducer.char.length", "4");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
IndexController(验证码、登录成功、跳转登录页面接口)
@Controller
public class IndexController {
public static Cache<String, String> codeCache = CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS).build();
@Autowired
private Producer producer;
@RequestMapping(value = "/login.html")
public String login(){
return "login";
}
/**
* 从 SecurityContextHolder 获取信息
* @return
*/
@RequestMapping(value = "/info", method = RequestMethod.GET)
@ResponseBody
public String index() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return JSON.toJSONString(authentication);
}
/**
* 从 SecurityContextHolder 获取信息
* @return
*/
@RequestMapping(value = "/verificate/code", method = RequestMethod.GET)
public void verificateCode(HttpServletResponse response) {
response.setContentType("image/jpg");
// 生成验证码
String verifyCode = producer.createText();
// 存入 guava 本地缓存
codeCache.put("codeCacheKey", verifyCode);
BufferedImage image = producer.createImage(verifyCode);
try(ServletOutputStream outputStream = response.getOutputStream()){
ImageIO.write(image,"jpg",outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
login.html(登录页面)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>
<form action="/login" method="post">
用户名:<input name="username" type="text"><br>
密码:<input name="password" type="password"><br>
验证码:<input name="code" type="text"><br>
<img src="/verificate/code">
<button type="submit">登陆</button>
</form>
</body>
</html>
验证码认证器,这里为了简单方便,验证码直接存缓存且 key
值固定,仅支持一个人测试使用
/**
* @ClassName KaptchaAuthenticationProvider
* @Desciption 自定义验证码认证器
* @Author MaRui
* @Date 2023/9/19 15:17
* @Version 1.0
*/
public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String verificateCode = codeCache.getIfPresent("codeCacheKey");
String inputVerificateCode = (String) request.getParameter("code");
if (!verificateCode.equals(inputVerificateCode)) {
throw new InternalAuthenticationServiceException("验证码错误");
}
return super.authenticate(authentication);
}
}
SecurityConfig 配置
/**
* @ClassName SecurityConfig
* @Desciption Security 配置
* @Author MaRui
* @Date 2023/9/18 17:29
* @Version 1.0
*/
@Configuration
public class SecurityConfig {
/**
* 采用内存的方式配置用户数据
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
UserDetails userDetails = User.withUsername("marui").password("{noop}1234").roles("admin").build();
return new InMemoryUserDetailsManager(userDetails);
}
/**
* 设置 kaptchaAuthenticationProvider 对应的 userDetailsService
* @return
*/
@Bean
public KaptchaAuthenticationProvider kaptchaAuthenticationProvider(){
KaptchaAuthenticationProvider kaptchaAuthenticationProvider= new KaptchaAuthenticationProvider();
kaptchaAuthenticationProvider.setUserDetailsService(userDetailsService());
return kaptchaAuthenticationProvider;
}
/**
* 自定义 AuthenticationManager
* @return
*/
@Bean
public AuthenticationManager authenticationManager(){
return new ProviderManager(kaptchaAuthenticationProvider());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) ->{
try {
auth.antMatchers("/verificate/code").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.failureForwardUrl("/login.html")
.defaultSuccessUrl("/info")
.permitAll()
.and()
.csrf().disable();
}
catch (Exception e){
}
});
return http.build();
}
}
验证一下,访问登录页面 http://127.0.0.1:8080/login.html,输入账号 marui
,密码 1234
,输入错误的验证码

输入账号 marui
,密码 1234
,输入正确的验证码登录成功
