shiro 和spring集合 实现登录时输入验证码并校验(七)
编写实现验证码的主体实现类:CaptchaCode
1 import java.util.UUID; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 6 import org.apache.commons.lang3.StringUtils; 7 import org.apache.shiro.cache.Cache; 8 import org.apache.shiro.cache.CacheManager; 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 import org.springframework.beans.factory.InitializingBean; 12 import org.springframework.util.Assert; 13 14 import com.itzixi.common.utils.CookieUtils; 15 16 17 /** 18 * 19 * @Title: CaptchaCode.java 20 * @Description: 验证码实现类 21 * @date 2017年10月14日 下午12:11:53 22 * @version V1.0 23 */ 24 public class CaptchaCode implements InitializingBean { 25 private final static Logger logger = LoggerFactory.getLogger(CaptchaCode.class); 26 private static final String DEFAULT_COOKIE_NAME = "itzixi-captcha"; 27 private final static String DEFAULT_CHACHE_NAME = "itzixiCaptchaCache"; 28 private final static int DEFAULT_MAX_AGE = -1; // cookie超时默认为session会话状态 29 30 private CacheManager cacheManager; 31 private String cacheName; 32 private String cookieName; 33 34 private Cache<String, String> itzixiCaptchaCache; 35 36 public CaptchaCode() { 37 this.cacheName = DEFAULT_CHACHE_NAME; 38 this.cookieName = DEFAULT_COOKIE_NAME; 39 } 40 41 public CaptchaCode(CacheManager cacheManager) { 42 this(); 43 this.cacheManager = cacheManager; 44 } 45 46 public CacheManager getCacheManager() { 47 return cacheManager; 48 } 49 50 public void setCacheManager(CacheManager cacheManager) { 51 this.cacheManager = cacheManager; 52 } 53 54 public String getCacheName() { 55 return cacheName; 56 } 57 58 public void setCacheName(String cacheName) { 59 this.cacheName = cacheName; 60 } 61 62 public String getCookieName() { 63 return cookieName; 64 } 65 66 public void setCookieName(String cookieName) { 67 this.cookieName = cookieName; 68 } 69 70 @Override 71 public void afterPropertiesSet() throws Exception { 72 Assert.notNull(cacheManager, "cacheManager must not be null!"); 73 Assert.hasText(cacheName, "cacheName must not be empty!"); 74 Assert.hasText(cookieName, "cookieName must not be empty!"); 75 this.itzixiCaptchaCache = cacheManager.getCache(cacheName); 76 } 77 78 /** 79 * 生成验证码 80 */ 81 public void generate(HttpServletRequest request, HttpServletResponse response) { 82 // 先检查cookie的uuid是否存在 83 String cookieValue = CookieUtils.getCookieValue(request, cookieName); 84 boolean hasCookie = true; 85 if (StringUtils.isBlank(cookieValue)) { 86 hasCookie = false; 87 cookieValue = UUID.randomUUID().toString(); 88 } 89 String captchaCode = CaptchaUtils.generateCode().toUpperCase();// 转成大写重要 90 // 不存在cookie时设置cookie 91 if (!hasCookie) { 92 CookieUtils.setCookie(request, response, cookieName, cookieValue, DEFAULT_MAX_AGE); 93 } 94 // 生成验证码 95 CaptchaUtils.generate(response, captchaCode); 96 itzixiCaptchaCache.put(cookieValue, captchaCode); 97 } 98 99 /** 100 * 仅能验证一次,验证后立即删除 101 * @param request HttpServletRequest 102 * @param response HttpServletResponse 103 * @param userInputCaptcha 用户输入的验证码 104 * @return 验证通过返回 true, 否则返回 false 105 */ 106 public boolean validate(HttpServletRequest request, HttpServletResponse response, String userInputCaptcha) { 107 if (logger.isDebugEnabled()) { 108 logger.debug("validate captcha userInputCaptcha is " + userInputCaptcha); 109 } 110 String cookieValue = CookieUtils.getCookieValue(request, cookieName); 111 if (StringUtils.isBlank(cookieValue)) { 112 return false; 113 } 114 String captchaCode = itzixiCaptchaCache.get(cookieValue); 115 if (StringUtils.isBlank(captchaCode)) { 116 return false; 117 } 118 // 转成大写重要 119 userInputCaptcha = userInputCaptcha.toUpperCase(); 120 boolean result = userInputCaptcha.equals(captchaCode); 121 if (result) { 122 itzixiCaptchaCache.remove(cookieValue); 123 CookieUtils.deleteCookie(request, response, cookieName); 124 } 125 return result; 126 } 127 }
CaptchaUtils.java
1 import java.awt.BasicStroke; 2 import java.awt.Color; 3 import java.awt.Font; 4 import java.awt.Graphics2D; 5 import java.awt.RenderingHints; 6 import java.awt.geom.QuadCurve2D; 7 import java.awt.image.BufferedImage; 8 import java.util.Random; 9 10 import javax.imageio.ImageIO; 11 import javax.servlet.ServletOutputStream; 12 import javax.servlet.http.HttpServletResponse; 13 14 /** 15 * 16 * @Title: CaptchaUtils.java 17 * @Description: 验证码工具类 18 * @date 2017年10月14日 下午12:13:14 19 * @version V1.0 20 */ 21 class CaptchaUtils { 22 // 默认的验证码大小 23 private static final int WIDTH = 108, HEIGHT = 40, CODE_SIZE = 4; 24 // 验证码随机字符数组 25 protected static final char[] charArray = "3456789ABCDEFGHJKMNPQRSTUVWXY".toCharArray(); 26 // 验证码字体 27 private static final Font[] RANDOM_FONT = new Font[] { 28 new Font("nyala", Font.BOLD, 38), 29 new Font("Arial", Font.BOLD, 32), 30 new Font("Bell MT", Font.BOLD, 32), 31 new Font("Credit valley", Font.BOLD, 34), 32 new Font("Impact", Font.BOLD, 32), 33 new Font(Font.MONOSPACED, Font.BOLD, 40) 34 }; 35 36 /** 37 * 生成验证码 38 */ 39 static void generate(HttpServletResponse response, String vCode) { 40 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); 41 response.setHeader("Pragma","no-cache"); 42 response.setHeader("Cache-Control","no-cache"); 43 response.setDateHeader("Expires", 0); 44 response.setContentType("image/jpeg"); 45 46 ServletOutputStream sos = null; 47 try { 48 drawGraphic(image, vCode); 49 sos = response.getOutputStream(); 50 ImageIO.write(image, "JPEG", sos); 51 sos.flush(); 52 } catch (Exception e) { 53 throw new RuntimeException(e); 54 } finally { 55 IOUtils.closeQuietly(sos); 56 } 57 } 58 59 // 生成随机类 60 private static final Random RANDOM = new Random(); 61 62 /** 63 * 生成验证码字符串 64 * @return 验证码字符串 65 */ 66 static String generateCode() { 67 int count = CODE_SIZE; 68 char[] buffer = new char[count]; 69 for (int i = 0; i < count; i++) { 70 buffer[i] = charArray[RANDOM.nextInt(charArray.length)]; 71 } 72 return new String(buffer); 73 } 74 75 private static void drawGraphic(BufferedImage image, String code){ 76 // 获取图形上下文 77 Graphics2D g = image.createGraphics(); 78 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 79 // 图形抗锯齿 80 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 81 // 字体抗锯齿 82 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 83 84 // 设定背景色,淡色 85 g.setColor(getRandColor(210, 250)); 86 g.fillRect(0, 0, WIDTH, HEIGHT); 87 88 // 画小字符背景 89 Color color = null; 90 for(int i = 0; i < 20; i++){ 91 color = getRandColor(120, 200); 92 g.setColor(color); 93 String rand = String.valueOf(charArray[RANDOM.nextInt(charArray.length)]); 94 g.drawString(rand, RANDOM.nextInt(WIDTH), RANDOM.nextInt(HEIGHT)); 95 color = null; 96 } 97 // 取随机产生的认证码(4位数字) 98 char[] buffer = code.toCharArray(); 99 for (int i = 0; i < buffer.length; i++){ 100 char _code = buffer[i]; 101 //旋转度数 最好小于45度 102 int degree = RANDOM.nextInt(28); 103 if (i % 2 == 0) { 104 degree = degree * (-1); 105 } 106 //定义坐标 107 int x = 22 * i, y = 21; 108 //旋转区域 109 g.rotate(Math.toRadians(degree), x, y); 110 //设定字体颜色 111 color = getRandColor(20, 130); 112 g.setColor(color); 113 //设定字体,每次随机 114 g.setFont(RANDOM_FONT[RANDOM.nextInt(RANDOM_FONT.length)]); 115 //将认证码显示到图象中 116 g.drawString("" + _code, x + 8, y + 10); 117 //旋转之后,必须旋转回来 118 g.rotate(-Math.toRadians(degree), x, y); 119 } 120 //图片中间曲线,使用上面缓存的color 121 g.setColor(color); 122 //width是线宽,float型 123 BasicStroke bs = new BasicStroke(3); 124 g.setStroke(bs); 125 //画出曲线 126 QuadCurve2D.Double curve = new QuadCurve2D.Double(0d, RANDOM.nextInt(HEIGHT - 8) + 4, WIDTH / 2, HEIGHT / 2, WIDTH, RANDOM.nextInt(HEIGHT - 8) + 4); 127 g.draw(curve); 128 // 销毁图像 129 g.dispose(); 130 } 131 132 /** 133 * 给定范围获得随机颜色 134 */ 135 private static Color getRandColor(int fc, int bc) { 136 if (fc > 255) 137 fc = 255; 138 if (bc > 255) 139 bc = 255; 140 int r = fc + RANDOM.nextInt(bc - fc); 141 int g = fc + RANDOM.nextInt(bc - fc); 142 int b = fc + RANDOM.nextInt(bc - fc); 143 return new Color(r, g, b); 144 } 145 }
IOUtils.java
1 import java.io.Closeable; 2 import java.io.IOException; 3 4 /** 5 * 6 * @Title: IOUtils.java 7 * @Description: 流工具类,继承自Spring 8 * @date 2017年10月14日 下午12:13:04 9 * @version V1.0 10 */ 11 public class IOUtils extends org.springframework.util.StreamUtils { 12 13 /** 14 * closeQuietly 15 * @param closeable 自动关闭 16 */ 17 public static void closeQuietly(Closeable closeable) { 18 try { 19 if (closeable != null) { 20 closeable.close(); 21 } 22 } catch (IOException ioe) { 23 // ignore 24 } 25 } 26 }
2、增加Bean的配置文件 在spring配置文件中增加:并采用缓存redis实现验证码的缓存存储
1 <!--此Bean 只是增加登录时的验证码处理,不是shiro必须的配置--> 2 <bean class="com.itzixi.web.utils.CaptchaCode"> 3 <!--<property name="cacheManager" ref="shiroEhcacheManager"/>--> 4 <property name="cacheManager" ref="shiroRedisCacheManager"></property> 5 <!-- 复用半小时缓存 --> 6 <property name="cacheName" value="cacheCaptcha"/> 7 </bean>
3、增加前端代码实现:
1 <div class="form-group"> 2 <label class="control-label visible-ie8 visible-ie9">密码</label> 3 <div id="input-error"> 4 <input class="form-control form-control-solid placeholder-no-fix form-group" type="text" 5 autocomplete="off" placeholder="验证码" name="captcha" required/> 6 <a> 7 <img id="captcha" alt="验证码" src="<%=request.getContextPath() %>/captcha.action" 8 data-src="<%=request.getContextPath() %>/captcha.action?time=" 9 style="width:94.5px;height:35px;"/> 10 </a> 11 </div> 12 </div>
在Controller增加/captcha.action 服务地址方法,调用验证码生成类进行验证码生成并加入到缓存中去
4、在Controller层增加代码实现 接收,验证动作,如:CenterController.doPostlogin(....)
1 @RequestMapping(value = "/login", method = RequestMethod.POST) 2 @ResponseBody 3 public LeeJSONResult doPostlogin(String username, String password, String captcha, 4 @RequestParam(value = "isRememberMe", defaultValue = "0") Integer isRememberMe, 5 HttpServletRequest request, HttpServletResponse response) { 6 7 if (StringUtils.isBlank(username)) { 8 return LeeJSONResult.errorMsg("用户名不能为空"); 9 } 10 if (StringUtils.isBlank(password)) { 11 return LeeJSONResult.errorMsg("密码不能为空"); 12 } 13 if (!captchaCode.validate(request, response, captcha)) { 14 return LeeJSONResult.errorMsg("验证码错误, 请重新输入..."); 15 } 16 return LeeJSONResult.ok(); 17 }