Java 前端加密传输后端解密以及验证码功能 您所在的位置:网站首页 javaweb登录功能密码明文显示 Java 前端加密传输后端解密以及验证码功能

Java 前端加密传输后端解密以及验证码功能

2023-08-23 13:07| 来源: 网络整理| 查看: 265

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下载

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 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()); } 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//该登录界面没有验证码字段,但是已经消耗掉了剩余次数,说明该页面是过期页面,需要重新登录 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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有