[Java实战]Spring Security 添加验证码(二十三)
[Java实战]Spring Security 添加验证码(二十三)
在现代的 Web 应用中,验证码是防止恶意攻击(如暴力破解、自动注册等)的重要手段之一。Spring Security 是一个功能强大的安全框架,提供了用户认证、授权等功能。本文将详细介绍如何在 Spring Security 中添加验证码功能,从而进一步增强应用的安全性。
一. 环境准备
- openJDK 17+:Spring Boot 3 要求 Java 17 及以上。
- Spring Boot 3.4.5:使用最新稳定版。
- 构建工具:Maven 或 Gradle(本文以 Maven 为例)。
二、验证码的作用
验证码(CAPTCHA,Completely Automated Public Turing test to tell Computers and Humans Apart)是一种区分用户是人类还是机器人的测试。常见的验证码类型包括:
- 图片验证码:用户需要识别并输入图片中的字符。
- 短信验证码:用户需要输入发送到手机的验证码。
- 邮箱验证码:用户需要输入发送到邮箱的验证码。
验证码的主要作用是:
- 防止暴力破解:限制非法登录尝试。
- 防止自动注册:限制恶意用户批量注册账号。
- 防止垃圾评论:限制自动发布垃圾评论。
三、Spring Security 添加验证码
1. 添加依赖
在 Spring Boot 项目中,添加验证码功能需要一些额外的依赖。首先,确保你的项目中已经添加了 Spring Security 和 Spring Web 的依赖。
<dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Starter Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- Thymeleaf --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- 图片验证码库 --><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>6.0.0</version><scope>provided</scope></dependency>
</dependencies>
2. 配置验证码生成器
使用 Kaptcha 库生成图片验证码。首先,配置 Kaptcha 的 Bean。
@Configuration
public class KaptchaConfig {@Beanpublic Producer kaptchaProducer() {Properties properties = new Properties();properties.put("kaptcha.border", "no");properties.put("kaptcha.textproducer.font.color", "black");properties.put("kaptcha.textproducer.char.space", "5");properties.put("kaptcha.image.width", "125");properties.put("kaptcha.image.height", "45");properties.put("kaptcha.textproducer.char.len", "5");properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");DefaultKaptcha kaptcha = new DefaultKaptcha();kaptcha.setConfig(new Config(properties));return kaptcha;}
}
3. 创建验证码控制器
创建一个控制器,用于生成和显示验证码图片。
@RestController
public class KaptchaController {@Autowiredprivate Producer kaptchaProducer;@GetMapping("/captcha")public void getKaptcha(HttpServletResponse response, HttpSession session) throws IOException, IOException {response.setDateHeader("Expires", 0);response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");response.addHeader("Cache-Control", "post-check=0, pre-check=0");response.setHeader("Pragma", "no-cache");response.setContentType("image/jpeg");String capText = kaptchaProducer.createText();session.setAttribute("captcha", capText);BufferedImage bi = kaptchaProducer.createImage(capText);ServletOutputStream out = response.getOutputStream();ImageIO.write(bi, "jpg", out);try {out.flush();} finally {out.close();}}
}
4. 修改登录页面
在登录页面login.html中添加验证码输入框和验证码图片。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Login</title>
</head>
<body>
<form th:action="@{/login}" method="post"><div><label>Username: <input type="text" name="username"></label></div><div><label>Password: <input type="password" name="password"></label></div><div><label>Captcha: <input type="text" name="captcha"></label></div><div><img th:src="@{/captcha}" alt="captcha" onclick="this.src='/captcha?'+new Date()" style="cursor:pointer;"></div><div><input type="submit" value="Sign In"></div>
</form>
</body>
</html>
5. 修改 SecurityConfig
在 SecurityConfig
中添加验证码校验逻辑。
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate CustomUserDetailsService userDetailsService;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.disable()).authorizeHttpRequests(authorize -> authorize.requestMatchers("/admin/**").hasRole("ADMIN").requestMatchers("/user/**").hasRole("USER").requestMatchers("/", "/home", "/register", "/captcha").permitAll().anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").permitAll().defaultSuccessUrl("/home", true)).logout(logout -> logout.permitAll()).addFilterBefore(new CaptchaFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
6. 创建验证码过滤器
创建一个自定义过滤器,用于校验验证码。
public class CaptchaFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 仅拦截登录请求(POST 方法)if ("POST".equalsIgnoreCase(request.getMethod())&& "/login".equals(request.getRequestURI())) {// 从前端获取验证码参数(根据方案一或二调整名称)String inputCaptcha = request.getParameter("captcha");String sessionCaptcha = (String) request.getSession().getAttribute("captcha");// 校验逻辑if (inputCaptcha == null || inputCaptcha.isEmpty()|| !inputCaptcha.equalsIgnoreCase(sessionCaptcha)) {// 清除旧验证码并记录错误request.getSession().removeAttribute("captcha");request.getSession().setAttribute("captchaError", "验证码错误");response.sendRedirect("/login?error");return;}// 验证通过后清除验证码(避免重复使用)request.getSession().removeAttribute("captcha");}filterChain.doFilter(request, response);}
}
四、高级用法
1. 短信验证码
除了图片验证码,你还可以使用短信验证码。以下是一个简单的实现示例:
添加依赖
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.0</version>
</dependency>
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>1.1.0</version>
</dependency>
配置短信服务
@Service
public class SmsService {public void sendSms(String phone, String code) {DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "your-access-key-id", "your-access-key-secret");IAcsClient client = new DefaultAcsClient(profile);CommonRequest request = new CommonRequest();request.setMethod(MethodType.POST);request.setDomain("dysmsapi.aliyuncs.com");request.setVersion("2017-05-25");request.setAction("SendSms");request.putQueryParameter("RegionId", "cn-hangzhou");request.putQueryParameter("PhoneNumbers", phone);request.putQueryParameter("SignName", "your-sign-name");request.putQueryParameter("TemplateCode", "your-template-code");request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");try {CommonResponse response = client.getCommonResponse(request);System.out.println(response.getData());} catch (ServerException e) {e.printStackTrace();} catch (ClientException e) {e.printStackTrace();}}
}
修改登录页面
在登录页面中添加短信验证码输入框。
<div><label>SMS Captcha: <input type="text" name="smsCaptcha"></label></div>
修改验证码过滤器
在验证码过滤器中添加短信验证码校验逻辑。
public class CaptchaFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if ("/login".equals(request.getRequestURI())) {String captcha = request.getParameter("captcha");String smsCaptcha = request.getParameter("smsCaptcha");String sessionCaptcha = (String) request.getSession().getAttribute("captcha");String sessionSmsCaptcha = (String) request.getSession().getAttribute("smsCaptcha");if (captcha == null || !captcha.equalsIgnoreCase(sessionCaptcha)) {request.getSession().setAttribute("captchaError", "Invalid captcha");response.sendRedirect("/login");return;}if (smsCaptcha == null || !smsCaptcha.equalsIgnoreCase(sessionSmsCaptcha)) {request.getSession().setAttribute("smsCaptchaError", "Invalid SMS captcha");response.sendRedirect("/login");return;}}filterChain.doFilter(request, response);}
}
五、常见问题与解决方案
1. 验证码不显示
原因:Kaptcha 配置不正确,或服务器未正确返回图片。
解决方案:
- 检查 Kaptcha 的配置是否正确。
- 确保
KaptchaController
中的getKaptcha
方法返回正确的图片。
2. 验证码校验失败
原因:验证码输入错误,或验证码已过期。
解决方案:
- 确保用户输入的验证码正确。
- 检查验证码的有效期是否过期。
3. 短信验证码发送失败
原因:短信服务配置不正确,或网络问题。
解决方案:
- 检查短信服务的配置是否正确。
- 确保网络连接正常。
六、总结
本文详细介绍了如何在 Spring Security 中添加验证码功能,包括图片验证码和短信验证码。通过合理使用验证码,可以显著增强应用的安全性。希望本文能帮助你更好地理解和使用 Spring Security 添加验证码。
如果你在使用过程中遇到任何问题,欢迎在评论区留言交流。感谢你的阅读,希望这篇文章对你有所帮助!