SpringSecurity-6-基于Filter实现图形验证码

简介: SpringSecurity中有多种方式实现图像验证码,使用自定义过滤器去处理验证码逻辑是最简单的方式,只要将过滤器添加到合适的位置,当登录的时候,对验证码进行校验,成功就放行,失败则抛出异常。

SpringSecurity-6-基于Filter实现图形验证码


SpringSecurity中有多种方式实现图像验证码,使用自定义过滤器去处理验证码逻辑是最简单的方式,只要将过滤器添加到合适的位置,当登录的时候,对验证码进行校验,成功就放行,失败则抛出异常。



图形验证流程

流程图如下

使用kaptcha生成图形验证码



kaptcha是谷歌提供的一款开源验证码jar包,只需简单配置就可以生成图片,源码地址:https://github.com/penggle/kaptcha

添加kaptcha依赖


在项目的pom.xml中添加相关依赖

        <!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>


验证码生成配置类

config``包下创建一个KaptchaCodeConfig类作为验证码生成配置类,代码如下

@Component
public class KaptchaCodeConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
        properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
        properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
        properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "36");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "28");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 图片效果
        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config); return defaultKaptcha;
    }
}


抽取属性


当然了这是一个死配置,我们一般会使用xxx.properties+@PropertySource将配置属性抽取出来,如果不了解这个注解的,可以查看我之前对SpringBoot常用注解汇总里面对这些注解都有介绍,具体实现如下:


kaptcha.properties的 实现

kaptcha.BORDER=yes
kaptcha.BORDER_COLOR=192,192,192
kaptcha.IMAGE_WIDTH=110
kaptcha.IMAGE_HEIGHT=36
kaptcha.FONT_COLOR=blue
kaptcha.FONT_SIZE=28
kaptcha.FONT_NAMES=宋体,楷体,微软雅黑
kaptcha.CHAR_LENGTH=4
kaptcha.OBSCURIFICATOR_IMPL=com.google.code.kaptcha.impl.ShadowGimpy
  • 重新实现KaptchaCodeConfig类
@Component
@PropertySource("classpath:kaptcha.properties")
public class KaptchaCodeConfig {
    @Value("${kaptcha.BORDER}")
    private String BORDER ;
    @Value("${kaptcha.BORDER_COLOR}")
    private String BORDER_COLOR ;
    @Value("${kaptcha.IMAGE_WIDTH}")
    private String IMAGE_WIDTH ;
    @Value("${kaptcha.IMAGE_HEIGHT}")
    private String IMAGE_HEIGHT ;
    @Value("${kaptcha.FONT_COLOR}")
    private String FONT_COLOR ;
    @Value("${kaptcha.FONT_SIZE}")
    private String FONT_SIZE ;
    @Value("${kaptcha.FONT_NAMES}")
    private String FONT_NAMES ;
    @Value("${kaptcha.CHAR_LENGTH}")
    private String CHAR_LENGTH ;
    @Value("${kaptcha.OBSCURIFICATOR_IMPL}")
    private String OBSCURIFICATOR_IMPL ;
    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty(Constants.KAPTCHA_BORDER, BORDER);
        properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, BORDER_COLOR);
        properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, IMAGE_WIDTH);
        properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, IMAGE_HEIGHT);
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, FONT_COLOR);
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, FONT_SIZE);
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, FONT_NAMES);
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH,CHAR_LENGTH); // 图片效果
        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, OBSCURIFICATOR_IMPL);
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config); return defaultKaptcha;
    }
}


生成图片的接口实现

使用CaptchaController实现验证码接口

@Slf4j
@RestController
public class CaptchaController {
    public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    @Autowired
    private DefaultKaptcha defaultKaptcha; /**
     * 获取图形验证码
     */
    @RequestMapping("/code/image")
    public void imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        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");
        // 1. 获取验证码字符串
        String code = defaultKaptcha.createText();
        log.info("生成的图形验证码是:" + code);
        // 2. 字符串把它放到session中
        request.getSession().setAttribute(SESSION_KEY , code);
        // 3. 获取验证码图片
        BufferedImage image = defaultKaptcha.createImage(code);
        // 4. 将验证码图片把它写出去
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(image, "jpg", out);
    }
}



/code/image接口免验证

因为这是登录页面使用的,所以要免登录,因此需要在LearnSrpingSecurity.configure(HttpSecurity http)

中放行 /code/image 资源权限

重构login.html

把如下代码加入到登录页面合适的位置,注意图片img标签放到登录表单中。

<!--suppress ALL-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>springboot葵花宝典登录页面</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>springboot葵花宝典登录页面</h1>
<form  th:action="@{/login/form}" action="index.html" method="post">
    <span>用户名称</span><input type="text" name="username" /> <br>
    <span>用户密码</span><input type="password" name="password" /> <br>
    <div >
        <input name="code" type="text" class="form-control" placeholder="验证码">
        <img onclick="this.src='/code/image?'+Math.random()" src="/code/image" alt="验证码" />
    </div>
    <div th:if="${param.error}">
        <span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}" style="color:#ff0000">用户名或 密码错误</span>
    </div>
    <input type="submit" value="登陆">
</form>
</body>
</html>


实现效果

重新启动项目,得到的登录页面效果如下

实现验证码校验过滤器


编写自己的过滤器ImageCodeValidateFilter,让其继承OncePerRequestFilter(可以保证每一次请求只使用一次该过滤器)


添加@Component注解


在ImageCodeValidateFilter过滤器中从seesion获取验证码文字与用户输入比对,比对通过执行其他过滤器链


比对不通过,抛出SessionAuthenticationException异常,交给AuthenticationFailureHandler处理,提示信息通过自定义异常 ValidateCodeExcetipn 抛出


最后将ImageCodeValidateFilter放在UsernamePasswordAuthenticationFilter表单过滤器之前执行

@Component
public class ImageCodeValidateFilter extends OncePerRequestFilter {
    @Autowired
    private MyAuthenticationFailureHandler failureHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 必须是登录的post请求才能进行验证,其他的直接放行
        if(StringUtils.equals("/login/form",request.getRequestURI())
                && StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
            try{
                //1.验证谜底与用户输入是否匹配
                validate(request);
            }catch(AuthenticationException e){
                //2.捕获步骤1中校验出现异常,交给失败处理类进行进行处理
                failureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }
        //通过校验,就放行
        filterChain.doFilter(request,response);
    }
    private void validate(HttpServletRequest request) throws ServletRequestBindingException {
        String sessionCode = (String)request.getSession().getAttribute(CaptchaController.SESSION_KEY);
        String inpuCode = request.getParameter("code");
        if(StringUtils.isEmpty(inpuCode)){
            throw new SessionAuthenticationException("验证码不能为空");
        }
        if(!inpuCode.equalsIgnoreCase(sessionCode)){
            throw new ValidateCodeException("验证码不能为空");
        }
    }
}


创建验证码异常类

在exception包下创建ValidateCodeException,继承AuthenticationException

org.springframework.security.core.AuthenticationException

import org.springframework.security.core.AuthenticationException;
public class ValidateCodeException extends AuthenticationException {
    public ValidateCodeException(String msg, Throwable cause) {
        super(msg, cause);
    }
    public ValidateCodeException(String msg) {
        super(msg);
    }
}



  • 修改MyAuthenticationFailureHandler类
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private  static ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 当认证失败后,响应 JSON 数据给前端
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
    }
}


重构LearnSrpingSecurity

  1. 将ImageCodeValidateFilter过滤器添加到UsernamePasswordAuthenticationFilter过滤器前面,需要在configure(HttpSecurity http)方法中进行修改


具体步骤:

  • 注入ImageCodeValidateFilter
@Autowired
private ImageCodeValidateFilter codeValidateFilter;


  • 把 ImageCodeValidateFilter 添加 UsernamePasswordAuthenticationFilter 实例前

  http.csrf().disable()  //禁用跨站csrf攻击防御,后面的章节会专门讲解
               .addFilterBefore(codeValidateFilter, UsernamePasswordAuthenticationFilter.class)

测试

  • 不输入验证码


7441c956f896f6b563e5f1ce8b21fee9.png



da1d2755c68d3b3117d046d808dace8e.png


  • 输入正确验证码



c16bcdba23e749ff204c4e4a2cc57ce7.png




如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!


目录
相关文章
|
存储
​SpringSecurity-7-自定义AuthenticationProvider实现图形验证码
上一章节我们介绍了如何使用过滤器(Filter)实现图形验证,这是属于Servlet层面,比较简单容易理解。那么这次我们介绍SpringSecurity提供的另一种比较高端的实现图形化验证码,这就是AuthenticationProvider自定义认证。
167 0
|
7月前
|
前端开发 JavaScript Java
springboot 集成easy-captcha实现图像验证码显示和登录
springboot 集成easy-captcha实现图像验证码显示和登录
425 0
|
7月前
|
NoSQL 前端开发 Java
spring boot3登录开发-2(1图形验证码接口实现)
spring boot3登录开发-2(1图形验证码接口实现)
253 2
|
人工智能 安全 前端开发
Spring Security系列教程13--基于过滤器实现图形验证码
前言 在前两个章节中,一一哥 带大家学习了Spring Security内部关于认证授权的核心API,以及认证授权的执行流程和底层原理。掌握了这些之后,对于Spring Security,我们不仅做到了 "知其然",而且也做到了 "知其所以然"! 在现在的求职环境下,只知道某个技能点的用法是远远不够的,面试官会要求我们研究某个技术的底层实现原理,所以虽然前面的两章内容掌握起来很有难度,但是还是希望各位小伙伴结合源码认真研读,这样你才能在编程之路上走的更远更高! 总是研究底层,对于我们初学者来说,既有难度,也会影响咱们的学习积极性,所以从本篇文章开始,咱们继续学习Spring Securit
400 0
|
JSON 安全 搜索推荐
​SpringSecurity-5-自定义登录验证
​SpringSecurity-5-自定义登录验证
168 0
|
XML Java Maven
去除jessionid时shiro设置sessionIdUrlRewritingEnabled报错,超级坑
使用shiro进行权限认证时,登录地址第一次访问总是自动携带JESSIONID,现在需要把它去掉不能显示。
284 0
|
Java
SpringBoot 彩色动态验证码使用!
SpringBoot 彩色动态验证码使用!
118 0
SpringBoot 彩色动态验证码使用!
|
前端开发 JavaScript NoSQL
|
存储 安全 Java
Spring Security系列教程14--基于自定义的认证提供器实现图形验证码
前言 在上一个章节中,一一哥 带大家实现了如何在Spring Security中添加执行自定义的过滤器,进而实现验证码校验功能。这种实现方式,只是实现验证码功能的方式之一,接下来我们再学习另一种实现方式,就是利用AuthenticationProvider来实现验证码功能,通过这个案例,我们学习如何进行自定义AuthenticationProvider。 一. 认证提供器简介 在上一章节中,我带各位利用自定义的过滤器实现了图形验证码效果,接下来我们利用另一种方式,基于自定义的认证提供器来实现图形验证码。 1. 认证提供器AuthenticationProvider 在第11章节中,壹哥 给大家
299 0