zoukankan      html  css  js  c++  java
  • js加密后台加密解密以及验证码

    该文档为转载内容:

        加密解密
        1 前端js加密概述
        2 前后端加密解密
                21 引用的js加密库
                22 js加密解密
                23 Java端加密解密PKCS5Padding与js的Pkcs7一致
        验证码
        1 概述
        2 验证码生成器
        3 控制器使用验证码 如 CodeController
        应用
        1 loginhtml
        2 Controller
        4 实现思路
    

    1. 加密解密
    1.1 前端js加密概述

    对系统安全性要求比较高,那么需要选择https协议来传输数据。当然很多情况下一般的web网站,如果安全要求不是很高的话,用http协议就可以了。在这种情况下,密码的明文传输显然是不合适的,因为如果请求在传输过程中被截了,就可以直接拿明文密码登录网站了。
    HTTPS(443)在HTTP(80)的基础上加入了SSL(Secure Sockets Layer 安全套接层)协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。传输前用公钥加密,服务器端用私钥解密。

    对于使用http协议的web前端的加密,只能防君子不能防小人。前端是完全暴露的,包括你的加密算法。
    知道了加密算法,密码都是可以破解的,只是时间问题。请看知乎上的一篇文章:对抗拖库

    所以加密是为了增加破解的时间成本,如果破解需要花费的时间让人难以接受,这也就达到了目的。

    而为了保证数据库中存储的密码更安全,则需要在后端用多种单向(非对称)加密手段混合进行加密存储。

    前端加密后端又需要解密,所以需要对称加密算法,即前端使用 encrypted = encrypt(password+key),后端使用 password = decrypt(encrypted +key) ,前端只传输密码与key加密后的字符串encrypted ,这样即使请求被拦截了,也知道了加密算法,但是由于缺少key所以很难破解出明文密码。所以这个key很关键。而这个key是由后端控制生成与销毁的,用完即失效,所以即使可以模拟用加密后的密码来发请求模拟登录,但是key已经失效了,后端还是验证不过的。

    注意,如果本地环境本就是不安全的,key被知道了,那就瞬间就可以用解密算法破解出密码了。这里只是假设传输的过程中被截获的情形。所以前端加密是防不了小人的。如果真要防,可以将加密算法的js文件进行压缩加密,不断更新的手段来使js文件难以获取,让黑客难以获取加密算法。变态的google就是这么干的,自己实现一个js虚拟机,通过不断更新加密混淆js文件让加密算法难以获取。这样黑客不知道加密算法就无法破解了。

    常用的对称加密算法有DES、3DES(TripleDES)、AES、RC2、RC4、RC5和Blowfis。可以参考:常用加密算法的Java实现总结

    这里采用js端与java端互通的AES加密算法。

    1.2 前后端加密解密

    1.2.1 引用的js加密库

        Cryptojs下载
    
        <script src="${request.contextPath}/resources/plugins/jQuery/jquery-2.2.4.min.js"></script>
        <script src="${request.contextPath}/resources/cryptojs/aes.js"></script>
        <script src="${request.contextPath}/resources/cryptojs/mode-ecb-min.js"></script>
    

    1.2.2 js加密解密

        var data = "888888";
        var srcs  = CryptoJS.enc.Utf8.parse(data);
        var key  = CryptoJS.enc.Utf8.parse('o7H8uIM2O5qv65l2');//Latin1 w8m31+Yy/Nw6thPsMpO5fg==
        function Encrypt(word){  
    
            var srcs = CryptoJS.enc.Utf8.parse(word);  
            var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});  
            return encrypted.toString();  
        }  
        function Decrypt(word){  
    
            var decrypt = CryptoJS.AES.decrypt(word, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});  
            return CryptoJS.enc.Utf8.stringify(decrypt).toString();  
        }
    

    这里key是页面加载的时候由服务器端生成的,用隐藏域保存。

    1.2.3 Java端加密解密(PKCS5Padding与js的Pkcs7一致)

        package com.jykj.demo.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;
    
        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");  
                kgen.init(128);  
    
                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);  
            }
        }
    

    2. 验证码
    2.1 概述

    验证码是用来区分人机的操作。
    验证码划代的标准是人机识别过程中基于对人类知识的应用。
    第一代:标准验证码
    这一代验证码是即是我们常见的图形验证码、语音验证码,基于机器难以处理复杂的计算机视觉及语音识别问题,而人类却可以轻松的识别来区分人类及机器。这一代验证码初步利用了人类知识容易解答,而计算机难以解答的机制进行人机判断。

    第二代:创新验证码
    第二代验证码是基于第一代验证码的核心思想(通过人类知识可以解答,而计算机难以解答的问题进行人机判断)而产生的创新的交互优化型验证码。第二代验证码基于第一代验证码的核心原理--“人机之间知识的差异”,拓展出大量创新型验证码。

    第三代:无知识型验证码
    第三代验证码最大的特点是不再基于知识进行人机判断,而是基于人类固有的生物特征以及操作的环境信息综合决策,来判断是人类还是机器。无知识型验证码最大特点即无需人类思考,从而不会打断用户操作,进而提供更好的用户体验。
    如Google的新版ReCaptcha、阿里巴巴的滑动验证。参考知乎 关于验证码

    2.2 验证码生成器

        package com.jykj.demo.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;
    
        /**
         * 验证码生成器
         * 
         */
        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;
            }
        }
    

    2.3 控制器使用验证码 如 CodeController

        @RequestMapping("/getCode.do")
            public void getCode(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. 应用
    实现功能:前端AES加密传输后端解密以及n次输入验证不通过后需要验证码
    有了上面的基础,实现起来应该不难了。

    3.1 login.html

        <input type="hidden" id="KEY" value="${Session.login_token}"/>
            <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();">
              </div>
              <div class="form-group has-feedback">
                <input type="password" class="form-control" id="password"
                    id="formPwd" onkeydown="javascript:if(event.keyCode==13) login();" >
              </div>
              <#if Session.login_failure_count?? && (Session.login_failure_count <=0) >
              <div class="form-group has-feedback">
                <input name="checkCode" onkeydown="javascript:if(event.keyCode==13)  login();" type="text" id="checkCode" maxlength="4" onblur="checkTheCode(this)" style="120px;"/> 
                <img src="getCode.do" id="CreateCheckCode" align="middle" title="点击刷新验证码" onclick="getCode()"  style="cursor: pointer;">
                <span id="checkCodeSpan" style="float: right;color: red;"></span>
              </div>
              </#if>
              <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();
              key = CryptoJS.enc.Utf8.parse(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 = '${request.contextPath}/';
              }else{
                  if(data.info=='timeout'){//可能已经就登录了,无需再次登录
                      alert('登录超时或已经登录!');
                      window.location.href = '${request.contextPath}/';
                  }else if('checkCode'==data.info){//需要验证码了
                      alert('用户名或密码错误!');
                      window.location.href = 'login';
                  }else if('codeError'==data.info){//验证码错误
                      getCode();
                      $('#checkCodeSpan').text('验证码错误');
                      $('#loginForm #checkCode').select();
                      $('#loginForm #checkCode').focus(); 
                  }else{
                      //登录失败,更新login_token
                      $('#KEY').val(data.data);
                      if($('#checkCodeSpan')){
                          $('#checkCodeSpan').text('');
                      }
                      alert(data.info);
                  }
              }
          }
         }) ;
        }
    
        function getCode(){
            $("#CreateCheckCode").attr('src',"getCode.do?nocache=" + new Date().getTime());  
        }
         </script>
    

    3.2 Controller

    在请求登录页面时需要后端生成一个随机的16位字符串的key,用于前后端加密解密用,该key在登录成功后销毁,存储在session中。

        @RequestMapping(value = "/login", method = RequestMethod.GET)
            public String login(){
                //生成login_token
                session.setAttribute(Helper.SESSION_LOGIN_TOKEN,RandomUtil.generateString(16));//登录令牌,用于密码加密的key,16位长度
                if(session.getAttribute(Helper.SESSION_USER) == null){
                    return "login";
                }
                else
                    return "redirect:/";
            }
    

    接下来是提交form表单的请求

        @RequestMapping(value = "/signIn", method = RequestMethod.POST,produces = "text/html;charset=UTF-8")
            @ResponseBody
            public String signIn(String username,String password,boolean remember,String checkCode) throws AuthorizationException{
                System.out.println(username+","+password+","+remember+","+checkCode);
                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?ConfigInfo.login_failure_count:Integer.parseInt(countObj.toString());
                System.out.println("剩余次数:"+count);
                //验证码逻辑
                if(count<=0){//需要验证码
                    Object oldCode = session.getAttribute(Helper.SESSION_CHECKCODE);
                    if(checkCode==null||oldCode==null){//该登录界面没有验证码字段,但是已经消耗掉了剩余次数,说明该页面是过期页面,需要重新登录
                        return JSON.toJSONString(new Result(false,"timeout"));//客户端需要重定向到主界面
                    }
                    if(checkCode.trim().isEmpty()) return JSON.toJSONString(new Result(false,"请输入验证码"));
                    if(oldCode.toString().equalsIgnoreCase(checkCode)){
                        //验证通过,可信客户端,给两次剩余次数
                        count=2;
                        session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,2);
                    }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 key = RandomUtil.generateString(16);//重新生成登录令牌,任何登录失败的操作都需要更新登录令牌
                ViewSysUser user =  sysUserService.selectUserPwd(username,password);
                if(user == null){
                    session.setAttribute(Helper.SESSION_LOGIN_TOKEN,key);
                    session.setAttribute(Helper.SESSION_LOGIN_FAILURE_COUNT,--count);//剩余次数-1
                    if(count<=0) return JSON.toJSONString(new Result(false,"checkCode"));//客户端需要重定向到登录界面将验证码显示出来
                    return JSON.toJSONString(new Result(false,"用户名或密码错误!",key));
                }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,loginUser);
                    sysEventService.insertEventLog(Helper.logTypeSecurity,username+" 登录系统");
                    return JSON.toJSONString(new Result(true,"登录成功!"));
                }
            }
    

    下面是生成随机数的工具类,很简单

        package com.jykj.demo.util;
    
        import java.util.Random;  
    
        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;  
            }          
        }  
    

    3.4 实现思路

        现在淘宝登录界面采用的是 无知识型验证码,只需要拖动滑块来判断是否是机器还是人,如果拖滑块验证失败,会弹出验证码输入或点击选择的框,来进行二次验证。若验证成功后,连续5次输入错误的用户名或密码,则又会弹出验证码来需要继续验证。也就是说有一个风险分析系统,如果满 
        足一定的条件(如连续多次输入错误等)则需要加强验证。风险分析系统要综合多种因素如ip,用户信息等等。Google更简单,通过点击复选框(I’m not a robot)就通过验证。
    
        刚开始时是不需要验证码的,用session来存储剩余次数,当连续5次验证都失败后,该计数递减为0,则后台判断该客户端不是很可信,需要验证码来加强验证,重新刷新登录界面(可以重定向实现)把验证码输入框加载出来。客户端需要同时提交账号密码以及验证码到后台验证,若验证码通 
         过验证,重新将次数复位(或自定义设置),表示该客户端暂时可信下次提交登录时可以不需要验证码。
    
        对于密码的加密传输与后端解密,key的生成与销毁的控制很关键。当加载登录页面时,由后台生成一个key给该页面,并保存到隐藏域中,同时该key也是存在session中。前端js用AES加密算法将密码和key混合加密,提交给后台,后台用相应的解密算法还原出原始密码,然后该原始密码用存                
         储时使用的多重混合加密算法进行加密与数据库中的密码匹配验证。当验证成功后,移除session中的key。而验证失败后的逻辑很关键,验证失败后,需要重新生成一个key给客户端,所以客户端通过返回的信息将key赋值到那个隐藏域字段中,这样达到刷新key的目的。
    
        总体来说,验证码的逻辑会有点复杂。验证码的验证最好放到后台来验证,如果放到前台就需要用一个隐藏域字段来存这个验证码,这样的话机器也可以获取到,那机器就不用识别图片就可以验证了,这样验证码就失去了作用。
  • 相关阅读:
    记Java程序Log4J漏洞的解决过程
    IIS中应用程序池自动停止,重启报错
    如何查看域名所对应的证书有效期?
    查看前端Vue版本命令
    WCF中常见的报错:The content type text
    dotnet 将自动代码格式化机器人带入团队 GitLab 平台
    WPF 引用第三方库的控件在设计器加上设计时数据和属性
    dotnet OpenXML 聊聊文本段落对齐方式
    WPF 布局 在有限空间内让两个元素尽可能撑开的例子
    dotnet 通过 DockerfileContext 解决项目放在里层文件夹导致 VisualStudio 构建失败
  • 原文地址:https://www.cnblogs.com/heavenTang/p/7306447.html
Copyright © 2011-2022 走看看