SpringSecurity图形验证码

前篇  

在上一篇中我们详细介绍了SpringSecurity自定义用户验证,也就是使用我们自己的登陆页面来进行验证,并且还介绍了登陆成功的和登陆失败的处理逻辑。在这一篇我们介绍一个很常见的登陆功能,图形验证码登陆。目的是防止登陆接口被频繁请求。因为SpringSecurity框架,默认不提供图形验证码登陆的逻辑,所以我们需要自己实现相应的逻辑,具体操作如下。

生成验证码


既然是图形验证码登陆,那我们当然要先生成图形验证码了。生成验证码的方式有很多,网上也有很多的例子,在这一篇,我们使用三方生成的验证码来演示,在实际的开发中,可根据公司的不同,自行实现。下面为三方的图形验证码的文档地址:
      

https://hutool.cn/docs/#/captcha/%E6%A6%82%E8%BF%B0

因为三方的验证码很简单,文档写的也比较全面,所以我们就不做过多介绍了。下面我们直接看生成图形验证码的代码。
  
Controller源码:  

@RestController
public class HelloWorldController {

    @Autowired
    private ShearCaptcha shearCaptcha;

    @GetMapping("/helloworld")
    public Object helloworld(HttpServletRequest request) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("data", "Hello World Spring Security");
        result.put("authentication", request.getAttribute("authentication"));
        return result;
    }

    @GetMapping("code/image")
    public void codeImage(HttpServletResponse response) throws IOException {
        shearCaptcha.createCode();
        shearCaptcha.write(response.getOutputStream());
    }
}  

上述代码中的code/image接口就是生成图形验证码的接口,有不了解的可以查看上面的三方接口文档。下面我们在自定义的登陆页面中添加一个验证码的输入框,并将我们刚刚生成图形验证码在登陆页面中显示出来。下面是具体的代码。

<div class="container-fluid">
    <div class="row-fluid">
        <div class="span12">
            <form method="post" action="/security/login/oneself">
                <fieldset>
                    <legend>登陆</legend>
                    <label>账号:</label><input type="text" name="username"/></span>
                    <label>密码:</label><input type="password" name="password"/></span>
                    <label>验证码:</label><input type="text" name="codeImage"/><img src="/security/code/image"></span>
                    <button type="submit" class="btn">提交</button>
                </fieldset>
            </form>
        </div>
    </div>
</div>  

这样我们就在自定义的登陆页面中添加了图形验证码的逻辑了。下面我们启动项目来先看一下效果。     

http://127.0.0.1:8088/security/helloworld
title

验证码校验  

下面我们来实现具体验证码登陆的逻辑。我们在之前的文章中介绍过SpringSecurity框架是由多个Filter链组成的。并且我们知道UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter拦截器我们可通过在配置文件中设置来决定此拦截器是否生效。而ExceptionTranslationFilter和FilterSecurityInterceptor拦截器则不可以通过配置文件设置,这两个拦截器一直会在拦截器链中发挥作用。当然我们也可以实现我们自己的拦截器来实现我们自己的业务逻辑。因为SpringSecurity框架默认是不提供图形验证码登陆的,所以为了我们能够实现上述的逻辑,我们必须要实现自己的拦截器。那实现我们自己的拦截器呢?如果熟悉SpringBoot框架的朋友,我们一定会知道SpringBoot中提供了OncePerRequestFilter抽象类,来帮助我们很方便的实现一个拦截器。并且该拦截器只有执行一次。因为SpringSecurity框架就是使用SpringBoot架构实现的。所以我们可以使用OncePerRequestFilter抽象类来创建我们自己的拦截器。下面为具体的代码:

@Component
public class CodeImageFilter extends OncePerRequestFilter {

    @Autowired
    private ShearCaptcha shearCaptcha;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, AuthenticationException {
        if (!request.getRequestURI().equals("/security/login/oneself")) {
            filterChain.doFilter(request, response);
            return;
        }
        String codeImage = request.getParameter("codeImage");
        if (StringUtils.isEmpty(codeImage)) {
            authenticationFailureHandler.onAuthenticationFailure(request,response,new CodeImageException("验证码为空"));
        }
        if (!shearCaptcha.verify(codeImage)) {
            authenticationFailureHandler.onAuthenticationFailure(request,response,new CodeImageException("验证码不正确"));
        }
        filterChain.doFilter(request, response);
    }
}  

下面我们在doFilterInternal方法实现我们图形验证码的验证逻辑。上述代码中我们判断了登陆的请求。这是因为按照咱们这个业务,图形验证码只会在登陆页面中会有,并且我们希望只是登陆时验证,而不是登陆的业务是不需要验证的。所以我们判断了登陆的请求路径。下面两个校验,一个是校验图形验证码是否为空,一个是校验图形验证码是否正确。并且上面使用到了AuthenticationFailureHandler拦截器。我们知道该拦截器的作用是异常拦截器。也就是当我们执行拦截器验证失败时,就会抛出相应的异常。所以我们正好用这个拦截器来抛出异常,这样这个异常就会被SpringSecurity框架拦截了。这就是我们实现图形验证码的验证逻辑。下面我们介绍一下怎么将这个拦截器添加到SpringSecurity框架中。因为我们之前已经介绍过很多次了,例如密码处理器、成功处理器和错误处理器。所以我们一样,在SpringSecurity框架的配置类中指定我们这个自定义的拦截器。具体代码如下:
   

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.csrf() // csrf攻击
      .disable()
      .addFilterBefore(codeImageFilter, UsernamePasswordAuthenticationFilter.class)
      .formLogin() // 表单登陆
      .loginPage("/login.html") // 登陆页面
      .loginProcessingUrl("/login/oneself") // 登陆表单提交请求
      .successHandler(successHandler) // 登陆成功处理器
      .failureHandler(failureHandler) // 登陆失败处理器
      .and()
      .authorizeRequests() // 对请求进行授权
      .antMatchers("/login.html", "/code/image") // 指定相应的请求
      .permitAll() // 不需要验证
      .anyRequest() // 任何请求
      .authenticated(); // 都需要身份认证
}  

在SpringSecurity框架添加一个拦截器很简单,只要我们使用addFilterBefore方法即可。并且该参数需要指定在哪个拦截器之前执行,我们指定的是UsernamePasswordAuthenticationFilter拦截器,也就是在执行账号和密码拦截器之前先执行我们的图形验证码拦截器。这样我们就实现了图形验证码登陆的逻辑了。下面我们启动项目测试一下。

http://127.0.0.1:8088/security/helloworld
title

当我们不输入任何验证码,然后点击登陆看一下效果。

title

我们看异常拦截器捕获到了我们自动抛出的异常。也就是图形验证码为空。下面我们故意输入错误验证码,来验证一下效果。

title
title

我们验证码拦截器成功检测到了,我们的验证码输入错误了,所以又抛出了异常。下面我们输入正确的验证码来看一下效果。

title

这样我们就登陆成功了。上述内容就是本篇的全部内容,如有不正确的内容,欢迎留言。谢谢。
  

项目源码

https://github.com/jilinwula/spring-security-helloworld3.git

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧