一.简介
SpringSecurity 默认是不支持验证码功能的,但是可以自己扩展,这也是使用SpringSecurity的好处之一,原生不支持,我们就自己扩展。
二.思路分析
因为系统默认的有一个DaoAuthenticationProvider 认证处理器,但是他只支持用户名和密码方式登录,所以是不能使用现有的认证器,那我们是不是可以实现一个自己的认证器,来覆盖这个默认的认证器呢?答案当然是可以的,大概实现思路是这样的:
- 创建一个认证器 继承默认的密码认证器DaoAuthenticationProvider
- 定义验证码认证器的逻辑
2.1. 从session获取保存的验证码
2.2. 从请求参数中获取用户输入的验证码
2.3. 比对验证码
2.4. 如果匹配成功,则调用DaoAuthenticationProvider的authenticate方法,进行原先逻辑认证
2.5. 如果匹配失败,则抛出异常,不走后面的逻辑 - 将自定义的provider加到AuthenticationManager中
这篇文章来看下如何通过自定义认证器来实现验证码校验的功能。
三.创建项目
如何创建一个SpringSecurity项目,前面文章已经有说明了,这里就不重复写了。
四.代码实现
4.1创建验证码处理器
4.1.1引入依赖
验证码的依赖:
com.github.penggle:kaptcha:2.3.2
4.1.2配置验证码
配置验证码的代码如下:
@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;
}
4.1.3创建验证码入口
创建验证码入口的代码如下:
@Autowired(required = false)
private Producer producer;
@RequestMapping("/kaptcha")
public void kaptcha(HttpServletResponse response, HttpSession session){
response.setContentType("image/jpg");
String text = producer.createText();
session.setAttribute("KAPTCHA_CODE",text);
BufferedImage image = producer.createImage(text);
try(ServletOutputStream outputStream = response.getOutputStream()){
ImageIO.write(image,"jpg",outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
4.2自定义验证码认证处理器
自定义验证码认证处理器代码如下:
public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String kaptchaCode = (String) request.getSession().getAttribute("KAPTCHA_CODE");
String inputKaptcha = request.getParameter("kaptcha");
if (!StrUtil.equals(kaptchaCode, inputKaptcha)) {
throw new InternalAuthenticationServiceException("验证码验证失败");
}
return super.authenticate(authentication);
}
}
4.3自定义登录页面
自定义登录页面的前端代码如下:
<!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="kaptcha" type="text"><br>
<img src="/kaptcha">
<button type="submit">登陆</button>
</form>
</body>
</html>
4.4配置SecurityFilterchain
配置SecurityFilterchain的代码如下:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) ->{
try {
auth.antMatchers("/kaptcha").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.failureForwardUrl("/login.html")
.permitAll()
.and()
.csrf().disable();
}
catch (Exception e){
}
});
return http.build();
}
4.5配置AuthenticationManager
配置AuthenticationManager的代码如下:
@Bean
public UserDetailsService userDetailsService(){
UserDetails userDetails = User.withUsername("memory1").password("{noop}memory1").roles("memory1").build();
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public KaptchaAuthenticationProvider kaptchaAuthenticationProvider(){
KaptchaAuthenticationProvider kaptchaAuthenticationProvider= new KaptchaAuthenticationProvider();
kaptchaAuthenticationProvider.setUserDetailsService(userDetailsService());
return kaptchaAuthenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(){
return new ProviderManager(kaptchaAuthenticationProvider());
}
五.验证登录
截图如下:
至此,扩展验证码功能就处理好了。