今天我們完成框架的thymeleaf模板顯示頁面功能,頁面的用戶登陸,密碼的AES加密解密,輸錯3次進行驗證碼驗證功能,東西可能比較多,這個是我這兩天在網上結合各種資源整合出來的,基本功能都已經實現,項目代碼我會附在文章的最后面。
1.thymeleaf模板顯示頁面功能
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在customer模塊的resources下創建static、templates文件夾,如下:

在static下放置一些頁面樣式的js和css,在templates文件夾下放置html頁面,此時在resources下的application.properties配置文件中配置thymeleaf,
#thymelea模板配置 spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML5 spring.thymeleaf.encoding=UTF-8 #熱部署文件,頁面不產生緩存,及時更新 spring.thymeleaf.cache=false spring.resources.chain.strategy.content.enabled=true spring.resources.chain.strategy.content.paths=/**
此時thymeleaf模板就配置完成了。
2.頁面的用戶登陸,AES加密解密:
關於AES的加密解密,我的實現思路是:首先用戶在瀏覽器中輸入網址跳轉到登錄頁面,此時在頁面中已經保存了從后台傳過來的key,該key值是后台隨機生成的,后台session中保存key值,前台頁面的隱藏框中也要保存key值,用於前端頁面的密碼加密以及后端的密碼解密,頁面刷新或用戶名、密碼輸入錯誤時,都會重新生成新的key值來替換原有保存的key。
首先在pom.xml中添加依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
用AES加密解密
a.前端加密用到的js有:aes.js和mode-ecb-min.js,就為了下載這兩個js花了我30個積分呢,[/哭],可以在static文件夾下創建一個aes文件夾,用於放置這兩個js。js下載地址:https://download.csdn.net/download/weixin_38340967/10677798
在templates下創建login.html,代碼如下:
<!DOCTYPE html> <!--<html xmlns:th="http://www.thymeleaf.org" >--> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8"> <title>welcome</title> <script type="text/javascript" src="../jquery/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="../jquery/jquery.easyui.min.js"></script> <script type="text/javascript" src="../aes/aes.js"></script> <script type="text/javascript" src="../aes/mode-ecb-min.js"></script> <!--<script th:src="@{jquery/jquery-1.11.1.min.js}"></script>--> <!--<script th:src="@{jquery/jquery-1.11.1.min.js}"></script>--> <!--<script th:src="@{jquery/jquery-1.11.1.min.js}"></script>--> <!--<script th:src="@{jquery/jquery-1.11.1.min.js}"></script>--> </head> <body> <input type="hidden" id="KEY" th:value="${model.loginToken}" /> <form action="signIn" method="post" id="loginForm"> <div class="form-group has-feedback"> <input type="text" class="form-control" placeholder="Email" name="username" onkeydown="javascript:if(event.keyCode==13) $('#password').focus();"> <span class="glyphicon glyphicon-envelope form-control-feedback"></span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" id="password" onkeydown="javascript:if(event.keyCode==13) login();" > <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> [[${session.SESSION_LOGIN_FAILURE_COUNT}]] <div class="form-group has-feedback" th:if="${session.SESSION_LOGIN_FAILURE_COUNT}<=0"> <input name="checkCode" onkeydown="javascript:if(event.keyCode==13) login();" type="text" id="checkCode" maxlength="4" style="width:120px;"/> <img src="getValidateCode" id="CreateCheckCode" align="middle" title="點擊刷新驗證碼" onclick="getCode()" style="cursor: pointer;"> <span id="checkCodeSpan" style="color: red;"></span> </div> <div class="row"> <div class="col-xs-8"> <div class="checkbox icheck"> <label> <input type="checkbox" name="remember" checked="checked" >記住密碼 </label> </div> </div> <!-- /.col --> <div class="col-xs-4"> <button type="button" onclick="login()" class="btn btn-primary btn-block btn-flat">登錄</button> </div> <!-- /.col --> </div> </form> <script> function login(){ $('#loginForm').form('submit',{ onSubmit: function(param){ var username = $('#loginForm input[name=username]').val(); if($.trim(username)==''){ alert('賬號不能為空!') $('#loginForm input[name=username]').focus(); return false; } var p = $('#loginForm #password').val(); if($.trim(p)==''){ alert('密碼不能為空!') $('#loginForm #password').focus(); return false; } var checkCodeInput = $('#loginForm #checkCode'); if(checkCodeInput.length>0){//判斷元素是否存在 var checkCode = checkCodeInput.val(); if($.trim(checkCode)=='' || checkCode.length!=4 ){ alert('請輸入4位驗證碼!') checkCodeInput.select(); checkCodeInput.focus(); return false; } } var key = $('#KEY').val(); // alert("key的值是: "+key); key = CryptoJS.enc.Utf8.parse(key); // alert("加密后key的值是: "+key); p = CryptoJS.enc.Utf8.parse($.trim(p)); var encrypted = CryptoJS.AES.encrypt(p, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7}); param.password = encrypted.toString(); }, success:function(data){ var data = eval('(' + data + ')'); if (data.success){ window.location.href = 'index'; }else{ if(data.msg=='timeout'){//可能已經就登錄了,無需再次登錄 alert('登錄超時或已經登錄!'); window.location.href = '${request.contextPath}/'; }else if('用戶名或密碼錯誤!'==data.msg){//需要驗證碼了 alert('用戶名或密碼錯誤!'); window.location.href = 'login'; }else if('codeError'==data.msg){//驗證碼錯誤 getCode(); $('#checkCodeSpan').text('驗證碼錯誤'); $('#loginForm #checkCode').select(); $('#loginForm #checkCode').focus(); }else{ //登錄失敗,更新login_token $('#KEY').val(data.data); if($('#checkCodeSpan')){ $('#checkCodeSpan').text(''); } alert(data.msg); } } }, }) ; } function getCode(){ var img = document.getElementById("CreateCheckCode"); img.src = "getValidateCode?nocache=" + new Date().getTime(); // $("#CreateCheckCode").attr('src',"getValidateCode?nocache=" + new Date().getTime()); } </script> </body> </html>
這里有一個坑,就是在html頁面中引入js的時候路徑的問題,可以看到我在頁面中是這樣寫的:<script type="text/javascript" src="../jquery/jquery-1.11.1.min.js"></script>,但是你仔細看應該是<script type="text/javascript" src="../static/jquery/jquery-1.11.1.min.js"></script>才對,為什么會少一個static呢,這是因為thymeleaf模板本身引入js時的路徑就是默認在static下的,要是加上static反而頁面會報js404錯誤,不信大家可以試試看。
var key = $('#KEY').val();
// alert("key的值是: "+key);
key = CryptoJS.enc.Utf8.parse(key); // alert("加密后key的值是: "+key); p = CryptoJS.enc.Utf8.parse($.trim(p)); var encrypted = CryptoJS.AES.encrypt(p, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7}); param.password = encrypted.toString();
這段代碼就是通過AES將用戶輸入的明文密碼和后台傳過來的key加密成密文,放到input框中提交到后台。
b.后台生成隨機的key值,並將前台傳過來的密文解密成明文密碼,
這里要在common模塊中寫幾個工具類:EncryptUtil用於密碼的加密解密,Helper用於記錄一些常量,RandomUtil用於隨機生成key值,Result用於向前台返回一個結果對象,樣式如下:

工具類代碼如下:
EncryptUtil:
package com.lj.common.util; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import sun.misc.BASE64Decoder; import java.security.SecureRandom; /** * Created with IntelliJ IDEA. * User: gaopeng * Date: 2018/9/17 0017 * Time: 17:30 * Description: */ public class EncryptUtil { private static final String KEY = "abcdefgabcdefg12"; private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding"; public static String base64Encode(byte[] bytes){ return Base64.encodeBase64String(bytes); } public static byte[] base64Decode(String base64Code) throws Exception{ return new BASE64Decoder().decodeBuffer(base64Code); } public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES")); return cipher.doFinal(content.getBytes("utf-8")); } public static String aesEncrypt(String content, String encryptKey) throws Exception { return base64Encode(aesEncryptToBytes(content, encryptKey)); } public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" ); secureRandom.setSeed(decryptKey.getBytes()); kgen.init(128,secureRandom); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES")); byte[] decryptBytes = cipher.doFinal(encryptBytes); return new String(decryptBytes); } public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception { return aesDecryptByBytes(base64Decode(encryptStr), decryptKey); } /** * 測試 * */ public static void main(String[] args) throws Exception { String content = "Test String么么噠"; //0gqIDaFNAAmwvv3tKsFOFf9P9m/6MWlmtB8SspgxqpWKYnELb/lXkyXm7P4sMf3e System.out.println("加密前:" + content); System.out.println("加密密鑰和解密密鑰:" + KEY); String encrypt = aesEncrypt(content, KEY); System.out.println(encrypt.length()+":加密后:" + encrypt); String decrypt = aesDecrypt(encrypt, KEY); System.out.println("解密后:" + decrypt); } }
Helper:
package com.lj.common.util; /** * Created with IntelliJ IDEA. * User: gaopeng * Date: 2018/9/17 0017 * Time: 17:08 * Description: */ public class Helper { public static final String SESSION_CHECKCODE = "SESSION_CHECKCODE"; public static final String SESSION_LOGIN_TOKEN = "SESSION_LOGIN_TOKEN"; public static final String SESSION_USER = "SESSION_USER"; public static final String SESSION_LOGIN_FAILURE_COUNT = "SESSION_LOGIN_FAILURE_COUNT"; public static final String logTypeSecurity = "logTypeSecurity"; public static final Integer COUNT = 3; }
RandomUtil:
package com.lj.common.util; import java.util.Random; /** * Created with IntelliJ IDEA. * User: gaopeng * Date: 2018/9/17 0017 * Time: 17:22 * Description: */ public class RandomUtil { public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String LETTERCHAR = "abcdefghijkllmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String NUMBERCHAR = "0123456789"; /** * 返回一個定長的隨機字符串(只包含大小寫字母、數字) * * @param length * 隨機字符串長度 * @return 隨機字符串 */ public static String generateString(int length) { StringBuffer sb = new StringBuffer(); Random random = new Random(); for (int i = 0; i < length; i++) { sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length()))); } return sb.toString(); } /** * 返回一個定長的隨機純字母字符串(只包含大小寫字母) * * @param length * 隨機字符串長度 * @return 隨機字符串 */ public static String generateMixString(int length) { StringBuffer sb = new StringBuffer(); Random random = new Random(); for (int i = 0; i < length; i++) { sb.append(LETTERCHAR.charAt(random.nextInt(LETTERCHAR.length()))); } return sb.toString(); } /** * 返回一個定長的隨機純大寫字母字符串(只包含大小寫字母) * * @param length * 隨機字符串長度 * @return 隨機字符串 */ public static String generateLowerString(int length) { return generateMixString(length).toLowerCase(); } /** * 返回一個定長的隨機純小寫字母字符串(只包含大小寫字母) * * @param length * 隨機字符串長度 * @return 隨機字符串 */ public static String generateUpperString(int length) { return generateMixString(length).toUpperCase(); } /** * 生成一個定長的純0字符串 * * @param length * 字符串長度 * @return 純0字符串 */ public static String generateZeroString(int length) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { sb.append('0'); } return sb.toString(); } /** * 根據數字生成一個定長的字符串,長度不夠前面補0 * * @param num * 數字 * @param fixdlenth * 字符串長度 * @return 定長的字符串 */ public static String toFixdLengthString(long num, int fixdlenth) { StringBuffer sb = new StringBuffer(); String strNum = String.valueOf(num); if (fixdlenth - strNum.length() >= 0) { sb.append(generateZeroString(fixdlenth - strNum.length())); } else { throw new RuntimeException("將數字" + num + "轉化為長度為" + fixdlenth + "的字符串發生異常!"); } sb.append(strNum); return sb.toString(); } /** * 每次生成的len位數都不相同 * * @param param * @return 定長的數字 */ public static int getNotSimple(int[] param, int len) { Random rand = new Random(); for (int i = param.length; i > 1; i--) { int index = rand.nextInt(i); int tmp = param[index]; param[index] = param[i - 1]; param[i - 1] = tmp; } int result = 0; for (int i = 0; i < len; i++) { result = result * 10 + param[i]; } return result; } }
Result:
package com.lj.common.util; import java.util.Date; /** * Created with IntelliJ IDEA. * User: gaopeng * Date: 2018/9/17 0017 * Time: 18:26 * Description: */ public class Result { private Boolean success; private String msg; private String key; public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Result(Boolean b, String s){ this.success = b; this.msg = s; } public Result(Boolean b, String s, String k){ this.success = b; this.msg = s; this.key = k; } }
在customer模塊中的controller中攔截/login和/signIn路徑的代碼如下:
@GetMapping("login") public ModelAndView login(HttpServletResponse response, HttpServletRequest request, Model model){ //生成login_token HttpSession session = request.getSession(); String key = RandomUtil.generateString(16); session.setAttribute(Helper.SESSION_LOGIN_TOKEN, key);//登錄令牌,用於密碼加密的key,16位長度 if(session.getAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT) == null){ session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT, Helper.COUNT);//登錄剩余失敗次數 } model.addAttribute("loginToken",key); System.out.println("傳到前台的key值為:"+ key); System.out.println("頁面跳轉到login.html"); // return "login"; // if(session.getAttribute(Helper.SESSION_USER) == null){ return new ModelAndView("login","model",model); // } // else // return "redirect:/"; }
@PostMapping(value = "signIn") @ResponseBody public String signIn(String username,String password,boolean remember,String checkCode,HttpServletRequest request, HttpServletResponse response){ System.out.println(username+","+password+","+remember+","+checkCode); HttpSession session = request.getSession(); Object token = session.getAttribute(Helper.SESSION_LOGIN_TOKEN);//原始令牌 if(token==null) return JSON.toJSONString(new Result(false,"timeout"));//登錄成功后token失效,則頁面失效,客戶端需要重定向到主界面 Object countObj = session.getAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT); int count = countObj==null?3:Integer.parseInt(countObj.toString()); System.out.println("剩余次數:"+count); //驗證碼邏輯 if(count<=0){//需要驗證碼 Object oldCode = session.getAttribute(Helper.SESSION_CHECKCODE); if(checkCode==null||oldCode==null){//該登錄界面沒有驗證碼字段,但是已經消耗掉了剩余次數,說明該頁面是過期頁面,需要重新登錄 System.out.println("chaoshila"); return JSON.toJSONString(new Result(false,"timeout"));//客戶端需要重定向到主界面 } if(checkCode.trim().isEmpty()) return JSON.toJSONString(new Result(false,"請輸入驗證碼")); if(oldCode.toString().equalsIgnoreCase(checkCode)){ //驗證通過,可信客戶端,給0次剩余次數 count=0; session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,count); }else{ return JSON.toJSONString(new Result(false,"codeError"));//驗證碼不正確,客戶端需要刷新驗證碼 } } //解密 try { password = EncryptUtil.aesDecrypt(password,token.toString());//解密后 System.out.println("Decrypt:"+password); } catch (Exception e) { e.printStackTrace(); return JSON.toJSONString(new Result(false,"timeout"));//客戶端需要重定向到主界面 } //登錄校驗 String loginKey = RandomUtil.generateString(16);//重新生成登錄令牌,任何登錄失敗的操作都需要更新登錄令牌 User user = null; System.out.println(user == null); if(user == null){ session.setAttribute(Helper.SESSION_LOGIN_TOKEN,loginKey); session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,--count);//剩余次數-1 System.out.println("剩余次數:" + session.getAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT)); //if(count<=0) return JSON.toJSONString(new Result(false,"checkCode",loginKey));//客戶端需要重定向到登錄界面將驗證碼顯示出來 System.out.println("這里直接要返回了!!!"); Result result = new Result(false,"用戶名或密碼錯誤!",loginKey); System.out.println("result對象的值是:" + result.getKey()); System.out.println(JSON.toJSONString(result)); return JSON.toJSONString(new Result(false,"用戶名或密碼錯誤!",loginKey)); }else{ // if(user.getUserid()!=ConfigInfo.admin_id && !user.getuStatus().equals(ConfigInfo.user_status_normal)) { // session.setAttribute(Helper.SESSION_LOGIN_TOKEN,key); // return JSON.toJSONString(new Result(false,"登錄失敗,該賬號已被禁止使用!",key)); // } //登錄成功 session.removeAttribute(Helper.SESSION_LOGIN_TOKEN); // loginUser = user; session.setAttribute(Helper.SESSION_USER,user); // sysEventService.insertEventLog(Helper.logTypeSecurity,username+" 登錄系統"); return JSON.toJSONString(new Result(true,"登錄成功!")); } }
上面生成key以及解密的部分都有注解,應該能看懂的。
c.輸錯3次顯示驗證碼
這里要實現驗證碼功能,首先要在common中寫一個工具類,用於生成驗證碼,代碼如下:
package com.lj.common.util; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Random; import javax.imageio.ImageIO; /** * Created with IntelliJ IDEA. * User: gaopeng * Date: 2018/9/17 0017 * Time: 16:57 * Description: */ public class ValidateCode { // 圖片的寬度。 private int width = 160; // 圖片的高度。 private int height = 28; // 驗證碼字符個數 private int codeCount = 4; // 驗證碼干擾線數 private int lineCount = 150; // 驗證碼 private String code = null; // 驗證碼圖片Buffer private BufferedImage buffImg = null; private char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; public ValidateCode() { this.createCode(); } /** * * @param width * 圖片寬 * @param height * 圖片高 */ public ValidateCode(int width, int height) { this.width = width; this.height = height; this.createCode(); } /** * * @param width * 圖片寬 * @param height * 圖片高 * @param codeCount * 字符個數 * @param lineCount * 干擾線條數 */ public ValidateCode(int width, int height, int codeCount, int lineCount) { this.width = width; this.height = height; this.codeCount = codeCount; this.lineCount = lineCount; this.createCode(); } public void createCode() { int x = 0, fontHeight = 0, codeY = 0; int red = 0, green = 0, blue = 0; x = width / (codeCount + 2);// 每個字符的寬度 fontHeight = height - 2;// 字體的高度 codeY = height - 4; // 圖像buffer buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = buffImg.createGraphics(); // 生成隨機數 Random random = new Random(); // 將圖像填充為白色 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); // 創建字體 Font font = new Font("Fixedsys", Font.BOLD, fontHeight); g.setFont(font); //干擾線 for (int i = 0; i < lineCount; i++) { int xs = random.nextInt(width); int ys = random.nextInt(height); int xe = xs + random.nextInt(width / 8); int ye = ys + random.nextInt(height / 8); red = random.nextInt(255); green = random.nextInt(255); blue = random.nextInt(255); g.setColor(new Color(red, green, blue)); g.drawLine(xs, ys, xe, ye); } // randomCode記錄隨機產生的驗證碼 StringBuffer randomCode = new StringBuffer(); // 隨機產生codeCount個字符的驗證碼。 for (int i = 0; i < codeCount; i++) { String strRand = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]); // 產生隨機的顏色值,讓輸出的每個字符的顏色值都將不同。 red = random.nextInt(255); green = random.nextInt(255); blue = random.nextInt(255); g.setColor(new Color(red, green, blue)); g.drawString(strRand, (i + 1) * x, codeY); // 將產生的四個隨機數組合在一起。 randomCode.append(strRand); } // 將四位數字的驗證碼保存到Session中。 code = randomCode.toString(); } public void write(String path) throws IOException { OutputStream sos = new FileOutputStream(path); this.write(sos); } public void write(OutputStream sos) throws IOException { ImageIO.write(buffImg, "png", sos); sos.close(); } public BufferedImage getBuffImg() { return buffImg; } public String getCode() { return code; } }
在customer模塊中的controller,編寫攔截/getValidateCode路徑的請求,用於生成驗證碼,代碼如下:
@GetMapping("getValidateCode") public void getValidateCode(HttpServletRequest reqeust, HttpServletResponse response) throws IOException { response.setContentType("image/jpeg"); // 禁止圖像緩存。 response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); HttpSession session = reqeust.getSession(); ValidateCode vCode = new ValidateCode(100, 28, 4, 100); session.setAttribute(Helper.SESSION_CHECKCODE, vCode.getCode()); vCode.write(response.getOutputStream()); }
驗證碼在3次輸入錯誤用戶名密碼后啟動的邏輯代碼在上面都已經貼出來了,這個3次是存儲在session中的,前台頁面要通過獲取session中的“可輸入錯誤剩余次數SESSION_LOGIN_FAILURE_COUNT”來判斷
至此,頁面的用戶登陸,密碼的AES加密解密,輸錯3次進行驗證碼驗證功能。演示效果如下:
第一次:

第二次:

第三次:

第三次以后的頁面就有驗證碼了:

下面是該項目的源碼下載地址:https://download.csdn.net/download/weixin_38340967/10677372
文章參考文獻:https://www.cnblogs.com/nicknailo/p/8947643.html
http://itfish.net/article/64414.html
