前端登录密码加密传输
在HTTPS还没有普及的时候,前端采用HTTP协议,登录用户名和密码在不做任何控制的情况下是明文传输的,大量的网站都需要登录,大量的人使用同样的用户名和密码。
目的:防止登录密码名文传输(仅仅只是防止明文传输,加密效果取决于key,而key对于前台是透明的)
方式:前端页面用js加密前端登录密码,采用AES对称加密
一、前端JS加密库crypto-js
因为懒,所以直接引入整个加密库,可以根据所需要的加密算法分别引入,下载地址crypto-js
如果是分别引入,记得别忘记引入核心库core
二、前端页面代码
因为需要加密密码,所以把提交方式换成了ajax
AES加密的key为128bit,可以理解为16个字符。key由后端生成并送入前端。
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" > <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="../../static/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}"> <script src="../../static/js/jquery-3.3.1.min.js" th:src="@{/js/jquery-3.3.1.min.js}"></script> <script src="../../static/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script> <script src="../../static/js/crypto-js.js" th:src="@{/js/crypto-js.js}"></script> <title>用户登录</title> <style> .bgColor { background-color: rgba(243, 66, 111, 0.15) } .divBorder { border: solid 1px rgba(12, 24, 255, 0.15); padding: 10px; margin-top: 10px; border-radius: 10px; text-align: center; vertical-align: middle; } .h4font { margin-top: 0px; font-family: 微软雅黑; font-weight: 500; } .center { padding: 20% 0; } .verifyInput { vertical-align: middle; font-size: 14px; font-weight: normal; line-height: 1; /*border:1px solid #999;*/ float: left; width: 180px; height: 30px; } .verifyImage { vertical-align: middle; float: right; height: 30px; } </style> </head> <body> <div class="container"> <div class="row center"> <div class="divBorder col-sm-offset-4 col-sm-4"> <h3 class="panel panel-heading h4font"> 用户登录 </h3> <h4 name="msg" th:text="${msg}"></h4> <input type="hidden" name="key" th:value="${key}"> <!--<form class="form-horizontal" th:action="@{/login}" method="post">--> <form class="form-horizontal" method="post"> <div class="input-group"> <span class="input-group-addon"><i class="glyphicon glyphicon-user" aria-hidden="true"></i></span> <input type="text" class="form-control" name="userName" placeholder="请输入用户名称" th:value="${userName}"/> </div> <br> <div class="input-group"> <span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span> <input type="password" class="form-control" name="password" th:value="${password}" placeholder="请输入密码"/> </div> <br/> <div class="input-group"> <span class="input-group-addon"><i class="glyphicon glyphicon-font"></i></span> <input type="text" class="verifyInput" name="verifyCode" placeholder="验证码"/> <img class="verifyImage" alt="验证码" onclick="this.src='/getVerifyCode?d='+new Date()*1" src="/getVerifyCode"> </div> <br> <input type="button" name="btnLogin" class="btn btn-lg btn-block btn-info" value="登 录"> </form> </div> </div> </div> <script th:inline="javascript"> $(function () { $('input[name="btnLogin"]').click(function () { var $key = $('input[name="key"]').val(); var $userName = $('input[name="userName"]').val(); var $password = $('input[name="password"]').val(); var $verifyCode = $('input[name="verifyCode"]').val(); // console.log($userName + ", " + $password + ", " + $verifyCode + ", " + $key); if ($userName == "" || $password == "" || $verifyCode == "") { alert("用户名、密码、验证码不能为空!"); return false; } var key = CryptoJS.enc.Utf8.parse($key); console.log("key:" + key + ",$key:" + $key); var password = CryptoJS.enc.Utf8.parse($password); var encrypted = CryptoJS.AES.encrypt(password, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}); var encryptedPwd = encrypted.toString(); // console.log("encrypted:" + encrypted); // console.log("encryptedPwd:" + encryptedPwd); var decrypt = CryptoJS.AES.decrypt(encryptedPwd, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); var testDecryptStr = CryptoJS.enc.Utf8.stringify(decrypt).toString(); // console.log("decrypt:" + decrypt); // console.log("testDecryptStr:" + testDecryptStr); $.ajax({ type: "post", url: "/login", data: {userName: $userName, password: encryptedPwd, verifyCode: $verifyCode,key: $key},
dataType: "json", success: function (result) { // console.log(result); if(result.success) { window.location.href=result.url; } else { $('h4[name="msg"]').html(result.msg); alert(result.msg); } // window.location.replace(result.url); // $('#container').load(result.url); }, error: function (result) { // console.log(result); alert(result.responseText); } }); }) }) </script> </body> </html>
三、后端解密代码
后端PKCS5Padding补码方式和前端的CryptoJS.pad.Pkcs7效果一致
public class AesUtils { private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding"; public static String encrypt(String content, String key) { try { byte[] raw = key.getBytes(); //获得密码的字节数组 SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥 Cipher cipher = Cipher.getInstance(ALGORITHMSTR); //根据指定算法ALGORITHM自成密码器 cipher.init(Cipher.ENCRYPT_MODE, skey); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥 byte [] byte_content = content.getBytes("utf-8"); //获取加密内容的字节数组(设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码 byte [] encode_content = cipher.doFinal(byte_content); //密码器加密数据 return Base64.encodeBase64String(encode_content); //将加密后的数据转换为字符串返回 } catch (Exception e) { e.printStackTrace(); return null; } } public static String decrypt(String encryptStr, String decryptKey) { try { byte[] raw = decryptKey.getBytes(); //获得密码的字节数组 SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥 Cipher cipher = Cipher.getInstance(ALGORITHMSTR); //根据指定算法ALGORITHM自成密码器 cipher.init(Cipher.DECRYPT_MODE, skey); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥 byte [] encode_content = Base64.decodeBase64(encryptStr); //把密文字符串转回密文字节数组 byte [] byte_content = cipher.doFinal(encode_content); //密码器解密数据 return new String(byte_content,"utf-8"); //将解密后的数据转换为字符串返回 } catch (Exception e) { e.printStackTrace(); return null; } } }
四、controller贴一把
@Controller public class HomeController { @Resource private LoginService loginService; @Resource DefaultKaptcha defaultKaptcha; @Resource LogService logService; private long verifyTTL = 60;//验证码过期时间60秒 private String create16String() { return RandomUtils.generateString(16); } @RequestMapping({"/", "/index"}) public String index() { return "index"; } /** * 2、生成验证码 * * @param request * @param response * @throws Exception */ @RequestMapping("/getVerifyCode") public void defaultKaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception { 略... } @RequestMapping(value = "/login", method = RequestMethod.GET) public String toLogin(Map<String, Object> map, HttpServletRequest request) { String key = create16String(); map.put("key",key); return "/user/login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public Object login(HttpServletRequest request) throws Exception { System.out.println("login()"); Map<String, Object> map = new HashMap<>(); String userName = request.getParameter("userName"); String encryptedPassword = request.getParameter("password"); String key = request.getParameter("key"); String verifyCode = request.getParameter("verifyCode"); String rightCode = (String) request.getSession().getAttribute("verifyCode"); Long verifyCodeTTL = (Long) request.getSession().getAttribute("verifyCodeTTL"); String password = AesUtils.decrypt(encryptedPassword,key); Long currentMillis = System.currentTimeMillis(); if (rightCode == null || verifyCodeTTL == null) { map.put("msg", "请刷新图片,输入验证码!"); map.put("userName", userName); map.put("success",false); map.put("url","/user/login"); return map; } Long expiredTime = (currentMillis - verifyCodeTTL) / 1000; if (expiredTime > this.verifyTTL) { map.put("msg", "验证码过期,请刷新图片重新输入!"); map.put("userName", userName); map.put("success",false); map.put("url","/user/login"); return map; } if (!verifyCode.equalsIgnoreCase(rightCode)) { map.put("msg", "验证码错误,请刷新图片重新输入!"); map.put("userName", userName); map.put("success",false); map.put("url","/user/login"); return map; // return "/user/login"; } LoginResult loginResult = loginService.login(userName, password); if (loginResult.isLogin()) { map.put("userName", userName); SysLog sysLog = LogFactory.createSysLog("登录","登录成功"); logService.writeLog(sysLog); map.put("success",true); map.put("url","/index"); return map; // return "/index"; } else { map.put("msg", loginResult.getResult()); map.put("userName", userName); map.put("success",false); map.put("url","/user/login"); return map; // return "/user/login"; } } }