我们在使用 Spring Security 时,大部分情况下都会向用户提供基于 Web 页面的登录表单,并且现在大部分的 Web 应用都会给用户提供基于手机验证码的登录方式,甚至未注册过的用户,可以在手机验证码验证通过后,直接创建用户,也就是,向非注册用户的手机号发送验证码。
这会带来短信验证码接口被恶意刷请求的问题,带来经济损失。一般有两种情况:
- 使用同一个手机号大量请求发送验证码
- 使用不同的手机号大量请求发送验证码
针对第一种情况,我们可以限制同一个手机号两次请求验证码的时间间隔。但是对于不同的手机号,就没办法判断是真实的请求还是恶意攻击了,通常的做法是,通过图形验证码,尽可能确保请求来自真人的操作,而不是机器人。
下面来介绍一下,如何给 Spring Security 的登录表单增加图形验证码的校验。
获取验证码
首先需要有一个获取图形验证码的请求,类似于这样:
@GetMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
/**
* 1.生成一个适当长度的随机字符串
* 2.将这个随机字符串保存到 Session 中
* 3.使用这个随机字符串生成一个验证码图片写入到 response 的输出流中。
*/
}
这部分比较简单,唯一麻烦的是图片的生成,代码繁琐而且网络上可以找到很多例子,因此就不在这里占用篇幅了。
接下来需要在登录表单中增加图片和输入验证码的文本框,类似这样:
<input type="text" name="captcha">
<img src="/captcha">
最后在 Spring Security 的配置中,允许获取验证码的接口匿名访问就可以了。
添加过滤器
Spring Security 的大部分逻辑都是通过过滤器来完成的,我们同样适用过滤器来完成图形验证码的校验。
代码如下:
public class CaptchaAuthenticationProcessingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(StringUtils.equals("/login",request.getRequestURI()) && StringUtils.endsWithIgnoreCase(request.getMethod(),"post")){
// 执行图形验证码的校验逻辑
}
filterChain.doFilter(request,response);
}
}
我们创建一个 CaptchaAuthenticationProcessingFilter
它继承自 OncePerRequestFilter
,这里需要实现 doFilterInternal
方法。这里的实现很简单,通过当前的请求路径,判断当前的请求路径是否需要校验验证码,如果需要的话,则进行校验。
之后,将这个过滤器添加到 Spring Security 的过滤器链中。在 Spring Security 配置类的 configure(HttpSecurity http)
方法中添加如下代码:
CaptchaAuthenticationProcessingFilter captchaAuthenticationProcessingFilter = new CaptchaAuthenticationProcessingFilter();
http.addFilterBefore(captchaAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);