zoukankan      html  css  js  c++  java
  • springboot使用kaptcha设置图形验证码

    kaptcha参数说明:

     
    Constant 描述 默认值
    kaptcha.border  图片边框,合法值:yes , no yes 
    kaptcha.border.color  边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. black
    kaptcha.border.thickness  边框厚度,合法值:>0  1
    kaptcha.image.width 图片宽 200
    kaptcha.image.height 图片高 50
    kaptcha.producer.impl 图片实现类 com.google.code.kaptcha.impl.DefaultKaptcha
    kaptcha.textproducer.impl 文本实现类 com.google.code.kaptcha.text.impl.DefaultTextCreator
    kaptcha.textproducer.char.string 文本集合,验证码值从此集合中获取 abcde2345678gfynmnpwx
    kaptcha.textproducer.char.length 验证码长度 5
    kaptcha.textproducer.font.names 字体 Arial, Courier
    kaptcha.textproducer.font.size 字体大小 40px.
    kaptcha.textproducer.font.color 字体颜色,合法值: r,g,b  或者 white,black,blue. black
    kaptcha.textproducer.char.space 文字间隔 2
    kaptcha.noise.impl 干扰实现类 com.google.code.kaptcha.impl.DefaultNoise
    kaptcha.noise.color 干扰线颜色,合法值: r,g,b 或者 white,black,blue. black
    kaptcha.obscurificator.impl 图片样式: 
    水纹com.google.code.kaptcha.impl.WaterRipple 
    鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
    阴影com.google.code.kaptcha.impl.ShadowGimpy
    com.google.code.kaptcha.impl.WaterRipple
    kaptcha.background.impl 背景实现类 com.google.code.kaptcha.impl.DefaultBackground
    kaptcha.background.clear.from 背景颜色渐变,开始颜色 light grey
    kaptcha.background.clear.to 背景颜色渐变, 结束颜色 white
    kaptcha.word.impl 文字渲染器 com.google.code.kaptcha.text.impl.DefaultWordRenderer
    kaptcha.session.key session key KAPTCHA_SESSION_KEY
    kaptcha.session.date session date KAPTCHA_SESSION_DATE
    一、springboot+shiro+kaptcha进行图片验证码
    1、jar包配置:
    1.1、maven中配置如下jar包
    <dependency>
        <groupId>com.github.penggle</groupId>
        <artifactId>kaptcha</artifactId>
        <version>2.3.2</version>
    </dependency>
    1.2、非maven配置去官网下载jar包导入到lib下:https://code.google.com/p/kaptcha/w/list
    2、kaptcha配置:
    package com.dymy.saas.common.config.verification;
    
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.google.code.kaptcha.util.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.Properties;
    
    /**
     * @Title: KaptchaUtils
     * @Author: 兵子
     * @Date: 2018/11/24 17:17:56
     * @Description: 验证码设置工具类
     */
    @Component
    public class KaptchaConfig {
    
        private final static String CODE_LENGTH = "4";
        private final static String SESSION_KEY = "verification_session_key";
    
        @Bean
    public DefaultKaptcha defaultKaptcha() {
            DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
            Properties properties = new Properties();
            // 设置边框
            properties.setProperty("kaptcha.border", "yes");
            // 设置边框颜色
            properties.setProperty("kaptcha.border.color", "105,179,90");
            // 设置字体颜色
            properties.setProperty("kaptcha.textproducer.font.color", "blue");
            // 设置图片宽度
            properties.setProperty("kaptcha.image.width", "118");
            // 设置图片高度
            properties.setProperty("kaptcha.image.height", "36");
            // 设置字体尺寸
            properties.setProperty("kaptcha.textproducer.font.size", "30");
            // 设置session key
            properties.setProperty("kaptcha.session.key", SESSION_KEY);
            // 设置验证码长度
            properties.setProperty("kaptcha.textproducer.char.length", CODE_LENGTH);
            // 设置字体
            properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,黑体");
            Config config = new Config(properties);
            defaultKaptcha.setConfig(config);
            return defaultKaptcha;
        }
    }        
     
    加@component注解,为了让springboot扫描到此配置类
    3、生成图片验证码:
    /**
     * @Author: 兵子
     * @Date: 2018/11/24 18:12
     * @Description: 获取验证码
     * @param:
    * @return:
    */
    @RequestMapping(value = "verification")
    public void getVerification(HttpServletRequest request, HttpServletResponse response) throws IOException {
        byte[] verByte = null;
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        try {
            //生产验证码字符串并保存到session中
         String createText = defaultKaptcha.createText();
            request.getSession().setAttribute("verify_session_Code", createText);
            //使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
         BufferedImage challenge = defaultKaptcha.createImage(createText);
            ImageIO.write(challenge, "jpg", jpegOutputStream);
        } catch (IllegalArgumentException e) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        } catch (IOException e) {
            e.printStackTrace();
        }
        //定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
       verByte = jpegOutputStream.toByteArray();
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        ServletOutputStream responseOutputStream = response.getOutputStream();
        responseOutputStream.write(verByte);
        responseOutputStream.flush();
        responseOutputStream.close();
    }
    将生成的验证码存入session中,然后将图片验证码以流的形式输出write;
    然后设置header(响应头),然后将此图片流响应回调用方
    4、html、jsp等前端页面写法
    <div class="form-ctrls clearfix">
             <label for="verify" class=""></label>
             <input type="text" id="verify" name="verifyCode" placeholder="请输入验证码" class="verify-input">
             <span class="v-code">
       <img src="/verification" alt="" onclick="this.src='/verification?d='+new Date()*1">
    </span>
        <i class="del"></i>
    </div>
    前端页面要想生成显示图片验证码,只需在img标签内src的属性写上生成图片验证码的请求地址即可,然后在添加点击事件,每次的点击请求一次生成图片验证码方法,替换原有的图片验证即可,此处写法可直接在onclick中给当前src替换即可,也可以单独写js点击事件,此处参数 d 是为了让页面不形成缓存进行实时刷新(参数可为随机数,推荐用时间毫秒不会重复)
    5、配合shiro进行验证码验证
    注意:如果后端用到了shiro则一定要对shiro进行配置,否则会出现无法生成图片验证码甚至连请求的方法都进不去
    5.1、shiroConfig配置:
    @Bean
    public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager,
                                                  KickoutSessionControlFilter kickoutSessionControlFilter) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 没有登陆的用户只能访问登陆页面
         shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
         shiroFilterFactoryBean.setSuccessUrl("/index");
            // 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度
         shiroFilterFactoryBean.setUnauthorizedUrl("/404");
    
            //自定义拦截器
         Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
            //限制同一帐号同时在线的个数。
          // filtersMap.put("kickout", kickoutSessionControlFilter);
            // 配置验证码过滤器
         filtersMap.put("kaptcha", shiroKaptchaFilter());
            shiroFilterFactoryBean.setFilters(filtersMap);
    
    
            // 权限控制map.
         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            filterChainDefinitionMap.put("/actuator", "anon");
            filterChainDefinitionMap.put("/actuator/**", "anon");
            filterChainDefinitionMap.put("/403", "anon");
            // 新的过滤
         filterChainDefinitionMap.put("/common/**", "anon");
    
            filterChainDefinitionMap.put("/error/**", "anon");
            // 添加验证码访问路径
         filterChainDefinitionMap.put("/verification", "anon");
            filterChainDefinitionMap.put("/login", "kaptcha,anon");
            filterChainDefinitionMap.put("/logout", "logout");
            filterChainDefinitionMap.put("/kickout", "anon");
            //filterChainDefinitionMap.put("/index2", "authc,kickout,perms[admin]");
            //filterChainDefinitionMap.put("/**", "authc,kickout");
         filterChainDefinitionMap.put("/index2", "authc,perms[admin]");
            filterChainDefinitionMap.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    要想生成图片验证码一定要配置此访问地址:
    anon:匿名访问,即不需要权限访问;
    authc:需要登录权限
     
    5.2、验证码过滤器(验证码校验)
     
     
     
    此处配置自定义过滤器,过滤器实际为一个map集合,将其key(kaptcha)再配置到访问路径中,则shiro在加在时会自动加载自定义过滤器,这里因为是验证码,只需要在登录时验证,所以只配置在了登录(login)路径中
     
    6、验证码校验
    package com.dymy.saas.common.filter;
    
    import com.dymy.saas.common.util.StringUtils;
    import com.google.common.collect.Maps;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.AccessControlFilter;
    import org.apache.shiro.web.util.WebUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Map;
    
    /**
     * @Title: ShiroKaptchaFilter
     * @Author: 兵子
     * @Date: 2018/11/26 14:14:41
     * @Description: 验证码验证过滤器
     */
    public class ShiroKaptchaFilter extends AccessControlFilter {
    
        // 页面提交的验证码参数
    private String LOGIN_KAPTCHA = "verifyCode";
        // 错误提示
    private String ERROR_KAPTCHA = "msg";
        // session中的验证码
    private String SHIRO_VERIFY_SESSION = "verify_session_Code";
        // 错误后的跳转地址
    private String ERROR_CODE_URL = "/login";
    
    
        @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            // 清除此提示,防止表单重复提示
    request.removeAttribute(ERROR_KAPTCHA);
            // 获取session中的验证码
    Subject subject = SecurityUtils.getSubject();
            String verCode = (String) subject.getSession().getAttribute(SHIRO_VERIFY_SESSION);
            // 获取提交的验证码
    String paramCode = request.getParameter(LOGIN_KAPTCHA);
            // 因为登录为表单提交登录,此处判断是否为表单提交
    if ("post".equalsIgnoreCase(request.getMethod())) {
                // 判断session中的验证码是否为空,为空则说明可能为第一次进入登录页面
    if (verCode != null) {
                    // 判断提交的验证码是否为空
    if (StringUtils.isNotBlank(paramCode)) {
                        // 验证码不区分大小写
    verCode = verCode.toLowerCase();
                        paramCode = paramCode.toLowerCase();
                        // 判断验证码是否一致
    if (paramCode.equals(verCode)) {
                            return true;
                        }
                    }
                    return false;
                }
            }
            return true;
        }
    
        @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
            // 重定向到登录页 ,并给出提示
    Map<String, String> map = Maps.newHashMap();
            map.put(ERROR_KAPTCHA, "验证码错误");
            WebUtils.issueRedirect(servletRequest, servletResponse, ERROR_CODE_URL, map);
            return false;
        }
    }
    验证码的校验过滤器需继承AccessControlFilter类,然后重写父类的isAccessAllowed和onAccessDenied方法;
    isAccessAllowed:
        此方法是表示是否允许访问,return true时表示允许访问,return false时表示不允许访问,可在此方法中操作判断是否允许访问;
    onAccessDenied:
        此方法表示在isAccessAllowed方法中如果不允许访问即return false,调用此方法然后在此方法中进行处理,当此方法也返回false时,则直接返回,后面的filter则都不执行(但是此处有可能会返回一个空页面,所以需要提前处理,进行重定向到登录页面),此时构建map集合,给出验证码错误消息提示然后重定向返回到登录页;
     
    7、登录方法处理
        注意:如果是shiro一般都会有两个登录方法,get和post,get登录方法是一个页面跳转方法,如访问www.baidu.com网址会跳转到百度首页,如果不是用的shiro则此处无用
    当在onAccessDenied方法进行重定向到login方法后,此时实际是要再走一遍登录login方法的,所以在login方法中获取之前重定向时传入的map集合参数,然后将其响应到登录页面,告诉用户验证码错误;
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginPage(Model model, HttpServletRequest request) {
        String msg = request.getParameter("msg");
        request.setAttribute("msg", msg);
        Operator currentLoginUser = RequestUtils.currentLoginUser();
        if (currentLoginUser != null && StringUtils.isNotEmpty(currentLoginUser.getLoginName())) {
            Integer operatorId = null;
            if (!Constants.ADMIN.equals(currentLoginUser.getLoginName())) {
                operatorId = currentLoginUser.getOperatorId();
            }
            List<Menu> menus = menuService.queryMenuByPidAndOperatorId(0, operatorId);
            model.addAttribute("menus", menus);
            model.addAttribute("user", currentLoginUser);
            return "redirect:/index";
        } else {
            return "login";
        }
    }
    通过request.getparamer("msg");取出之前重定向到登录时的map中的提示信息,然后可以request.setattribute("msg",msg);也可以model.addattribute("msg",msg);将提示信息放入域中,然后在页面取出即可展示给用户;
     
    最后,如果生成的图片验证码不是很清晰的话可以设置图片的样式、文字颜色和文字间隔,使其更清晰
     
     
    如何去掉干扰线:
     
     
  • 相关阅读:
    Android手势锁实现
    网页模板pug基本语法
    React入门看这篇就够了
    我曾站在离你最近的天涯
    一文看懂浏览器事件循环
    Vi编辑网卡
    2019.6.11_MySQL进阶二:主键与外键
    2019.6.13_笔试题目及答案
    2019.6.13_MySQL简单命令的使用
    2019.6.13_SQL语句中----删除表数据drop、truncate和delete的用法
  • 原文地址:https://www.cnblogs.com/ShaYeBlog/p/13358592.html
Copyright © 2011-2022 走看看