在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";
}
}
}

