編寫實現驗證碼的主體實現類: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 }