自定义过滤器:实现验证码功能
907字约3分钟
2024-08-08
思路分析
在 HttpSecurity
配置中,每新增一种配置,都会加入一个过滤器,或者覆盖默认的过滤器,我们使用的表单登录也同样是使用的过滤器:UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
1、写一个过滤器,继承
UsernamePasswordAuthenticationFilter
2、验证验证码,不正确抛出异常
InternalAuthenticationServiceException
3、验证码验证正确,调用父类
attemptAuthentication(request, response)
,完成用户认证
代码实现
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.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</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>
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>
IndexController(验证码、登录页转发接口)
/**
* @ClassName IndexController
* @Desciption 首页控制层
* @Author MaRui
* @Date 2023/8/10 18:00
* @Version 1.0
*/
@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 = "/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);
}
}
}
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;
}
}
验证码验证逻辑与自定义认证器大致一样,只是验证通过是调用父类 attemptAuthentication
方法
/**
* @ClassName KaptchaFilter
* @Desciption 验证码过滤器
* @Author MaRui
* @Date 2023/9/20 11:11
* @Version 1.0
*/
public class KaptchaFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String verificateCode = codeCache.getIfPresent("codeCacheKey");
String inputVerificateCode = request.getParameter("code");
if (!verificateCode.equals(inputVerificateCode)) {
throw new InternalAuthenticationServiceException("验证码错误");
}
return super.attemptAuthentication(request, response);
}
}
/**
* @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);
}
@Bean
public AuthenticationManager authenticationManager(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
return new ProviderManager(daoAuthenticationProvider);
}
/**
* 配置验证码过滤器
* @return
*/
@Bean
public KaptchaFilter kaptchaFilter(){
KaptchaFilter kaptchaFilter = new KaptchaFilter();
kaptchaFilter.setAuthenticationManager(authenticationManager());
kaptchaFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Map<String,Object> result = new HashMap<>();
result.put("code", "500");
result.put("msg","登录失败");
result.put("data",exception.getMessage());
renderString(response, JSONObject.toJSONString(result));
}
});
kaptchaFilter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String,Object> result = new HashMap<>();
result.put("code", "000000");
result.put("msg","登录成功");
result.put("data",authentication);
renderString(response, JSONObject.toJSONString(result));
}
});
return kaptchaFilter;
}
@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")
.permitAll()
.and().addFilterAt(kaptchaFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
catch (Exception e){
}
});
return http.build();
}
public static String renderString(HttpServletResponse response, String string) {
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
验证一下,访问登录页面 http://127.0.0.1:8080/login.html

输入账号 marui
,密码 1234
,输入错误的验证码
