使用RSA非对称加密算法对密码进行加密,能够保证传输数据的安全性。RSA公钥和私钥在服务器端生成,并且把私钥保存到服务器,把公钥的模数和指数传递给前端。前端根据模数和指数对密码进行加密,将密码密文传递给服务器。服务器根据私钥对密文进行解密,最后完成登录验证。本文主要介绍:“前端登录页面HTML代码”、“前端请求后端,获取RSA公钥的模数和指数”、“后端生成RSA公钥的模数和指数”、“将公钥的模数和指数返回给前端”、“前端根据公钥的模数和指数对密码进行加密”、“前端将手机号和密码密文传递给后端”、“后端使用私钥对密码密文进行解密”、“后端验证账号密码是否正确,完成登录验证”。

1、前端登录页面HTML代码。

<div id="loginMain">
		<div id="loginPic">
			<img src="../../images/login.png" alt="">
		</div>
		<div id="loginInfomation" class="layui-form">
			<p>
				<img src="../../images/name.png" alt="">
			</p>
			<h4 style="line-height:50px;">欢迎登录博销宝管理后台</h4>
			<h4 style="font-size:15px; line-height:16px; margin-bottom: 15px;">v1.0.0</h4>
			<div class="layui-form-item">
				<label class="layui-form-label"><strong
					class="requiredField">*</strong>公司编号:</label>
				<div class="layui-input-block">
					<input type="text" class="layui-input"
						name="${staffField.FIELD_NAME_companySN}" lay-verify="companySN"
						placeholder="请输入公司编号" maxlength="8" />
				</div>
			</div>
			<div class="layui-form-item">
				<label class="layui-form-label"><strong
					class="requiredField">*</strong>手机号码:</label>
				<div class="layui-input-block">
					<input type="text" class="layui-input"
						name="${staffField.FIELD_NAME_phone}" lay-verify="phone"
						maxlength="11" placeholder="请输入手机号码" />
				</div>
			</div>
			<div class="layui-form-item">
				<label class="layui-form-label"><strong
					class="requiredField">*</strong>密码:</label>
				<div class="layui-input-block">
					<input type="password" class="layui-input"
						name="${staffField.FIELD_NAME_pwdEncrypted}" lay-verify="password"
						maxlength="16" placeholder="请输入密码" />
				</div>
			</div>
			<p>
				<button class="layui-btn layui-btn-lg layui-btn-normal" lay-submit
					lay-filter="login">登录</button>
			</p>
			<p>
				<img src="../../images/logoName.png" alt="">
			</p>
			<input type="hidden" id="CURRENT_ReleaseNbrVersionNO" value="2.0.0" /><br>
		</div>
	</div>

2、前端请求后端,获取RSA公钥的模数和指数。

将手机号作为参数传递给后端:

//登录系统
	form.on("submit(login)", function(data){
		var loading = layer.load(1);
		var loginInfo = data.field;
		var password = loginInfo.pwdEncrypted;
		$.ajax({
			url: staffGetToken_url,
			type: method_post,
			dataType: "JSON",
			data: {"phone": loginInfo.phone},
			cache: false,
			async: true,
			success : function(data){
				var modulus = data.rsa.modulus;
				var exponent = data.rsa.exponent;
				var rsa = new RSAKey();
				rsa.setPublic(modulus, exponent);
				var res = rsa.encrypt(password);
				if(res){
					loginInfo.pwdEncrypted = res;
					$.ajax({
						url: staffLogin_url,
						type: method_post,
						async: true,
						dataType: "JSON",
						data: loginInfo,
						success: function(data){
							layer.close(loading);
							if(data){
								if(data.ERROR != "EC_NoError"){
									if(data.msg){
										layer.msg(data.msg);
									}else{
										layer.msg("手机号码或密码错误");
									}
									isToSend = true;
									return;
								}
								window.location.href = "../home.bx";
							}else{
								isToSend = true;
								layer.msg("登录失败,公司编号错误");
							}
						},
						error: function(){
							isToSend = true;
							layer.close(loading);
							layer.msg("服务器错误");
						}
					});
				}else{
					isToSend = true;
					layer.close(loading);
					layer.msg("登录失败");
				}
			},
			error: function(){
				isToSend = true;
				layer.close(loading);
				layer.msg("服务器错误");
			}
		});
		return false;
	})

3、后端生成RSA公钥的模数和指数。

(1)将生成的modulus模数和exponent指数放在RSAInfo类:

RSAInfo rsa = generateRSA(staff.getPhone());

	public RSAInfo generateRSA(String id) throws Exception {
		HashMap<String, Object> map = RSAUtils.getKeys();
		RSAPublicKey publicKey = (RSAPublicKey) map.get("public");

		mapRSA.put(id, map);

		String modulus = publicKey.getModulus().toString(16);
		String exponent = publicKey.getPublicExponent().toString(16);

		RSAInfo rsa = new RSAInfo();
		rsa.setExponent(exponent);
		rsa.setModulus(modulus);

		return rsa;
	}

(2)生成RSA公钥和私钥,放在hashmap中:

/**
	 * 生成公钥和私钥
	 * 
	 * @throws NoSuchAlgorithmException
	 *
	 */
	public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException {
		HashMap<String, Object> map = new HashMap<String, Object>();
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
		keyPairGen.initialize(1024);
		KeyPair keyPair = keyPairGen.generateKeyPair();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		map.put("public", publicKey);
		map.put("private", privateKey);
		return map;
	}

(3)将公钥和私钥信息以手机号为key保存到hashmap缓存,用于后面的解密:

HashMap<String, Object> map = RSAUtils.getKeys();
		RSAPublicKey publicKey = (RSAPublicKey) map.get("public");

		mapRSA.put(id, map);

(4)获取公钥的模数和指数:

String modulus = publicKey.getModulus().toString(16);
		String exponent = publicKey.getPublicExponent().toString(16);

		RSAInfo rsa = new RSAInfo();
		rsa.setExponent(exponent);
		rsa.setModulus(modulus);

4、将公钥的模数和指数返回给前端。

params.put("rsa", rsa);
		params.put(BaseAction.JSON_ERROR_KEY, EnumErrorCode.EC_NoError.toString());
		params.put(KEY_HTMLTable_Parameter_msg, staffBO.getLastErrorMessage());
		logger.info("返回的数据=" + params);

		return JSONObject.fromObject(params, JsonUtil.jsonConfig).toString();

5、前端根据公钥的模数和指数对密码进行加密。

获取模数和指数:

success : function(data){
				var modulus = data.rsa.modulus;
				var exponent = data.rsa.exponent;
				var rsa = new RSAKey();
				rsa.setPublic(modulus, exponent);
				var res = rsa.encrypt(password);

对密码进行加密:

// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
function RSAEncrypt(text) {
  var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
  if(m == null) return null;
  var c = this.doPublic(m);
  if(c == null) return null;
  var h = c.toString(16);
  if((h.length & 1) == 0) return h; else return "0" + h;
}

6、前端将手机号和密码密文传递给后端。

if(res){
					loginInfo.pwdEncrypted = res;
					$.ajax({
						url: staffLogin_url,
						type: method_post,
						async: true,
						dataType: "JSON",
						data: loginInfo,
						success: function(data){
							layer.close(loading);
							if(data){
								if(data.ERROR != "EC_NoError"){
									if(data.msg){
										layer.msg(data.msg);
									}else{
										layer.msg("手机号码或密码错误");
									}
									isToSend = true;
									return;

7、后端使用私钥对密码密文进行解密。

根据手机号key找到对应的私钥进行解密:

String pwd = decrypt(((BaseAuthenticationModel) bmIn).getKey(), sPasswordEncrypted);
		if (pwd == null) {
			return null;
		}

解密密文:

public String decrypt(String key, String sPasswordEncrypted) {
		lastErrorCode = ErrorInfo.EnumErrorCode.EC_NoError;
		lastErrorMessage = "";
		HashMap<String, Object> map = mapRSA.get(key);
		if (map == null) {
			return null;
		}
		RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private");
		//
		String pwd = "";
		try {
			pwd = RSAUtils.decryptByPrivateKey(sPasswordEncrypted, privateKey);
			if (!FieldFormat.checkRawPassword(pwd)) {
				lastErrorCode = ErrorInfo.EnumErrorCode.EC_WrongFormatForInputField;
				lastErrorMessage = FieldFormat.FIELD_ERROR_Password;
				logger.info(FieldFormat.FIELD_ERROR_Password);
				return null;
			}
			// logger.info("String decrypted=" + pwd);
		} catch (Exception e) {
			logger.info(e);
			lastErrorCode = EnumErrorCode.EC_Hack;
			return null;
		}

		return pwd;
	}

8、后端验证账号密码是否正确,完成登录验证。

DataSourceContextHolder.setDbName(dbName);
		BaseModel bm = retrieve1Object(BaseBO.SYSTEM, iUseCaseID, bmIn);

		String md5 = MD5Util.MD5(pwd + BaseAction.SHADOW);
		if (md5 == null) {
			lastErrorCode = ErrorInfo.EnumErrorCode.EC_OtherError;
			return null;
		}
		if (md5.equals(((BaseAuthenticationModel) bm).getSalt())) {
			return (BaseAuthenticationModel) bm;
		} else {
			lastErrorCode = ErrorInfo.EnumErrorCode.EC_NoSuchData;
		}