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);将提示信息放入域中,然后在页面取出即可展示给用户;
 
最后,如果生成的图片验证码不是很清晰的话可以设置图片的样式、文字颜色和文字间隔,使其更清晰
 
 
如何去掉干扰线:
 
 
posted @ 2020-07-22 08:48  沙耶  阅读(885)  评论(0编辑  收藏  举报