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);将提示信息放入域中,然后在页面取出即可展示给用户;
最后,如果生成的图片验证码不是很清晰的话可以设置图片的样式、文字颜色和文字间隔,使其更清晰
如何去掉干扰线: