kaptcha参数说明: 一、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下:/p/kaptcha/w/list 2、kaptcha配置:
package com.mon.config.verification;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.google.code.kaptcha.util.Config;import org.springframework.context.annotation.Bean;import org.ponent;import java.util.Properties;/*** @Title: KaptchaUtils* @Author: 兵子* @Date: /11/24 17:17:56* @Description: 验证码设置工具类*/@Componentpublic class KaptchaConfig {private final static String CODE_LENGTH = "4";private final static String SESSION_KEY = "verification_session_key";@Beanpublic 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 keyproperties.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: /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配置:
@Beanpublic 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.mon.filter;import com.mon.util.StringUtils;import mon.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: /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";@Overrideprotected 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;}@Overrideprotected 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登录方法是一个页面跳转方法,如访问网址会跳转到百度首页,如果不是用的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);将提示信息放入域中,然后在页面取出即可展示给用户; 最后,如果生成的图片验证码不是很清晰的话可以设置图片的样式、文字颜色和文字间隔,使其更清晰 如何去掉干扰线: