一、技術(shù)背景介紹
- 后端:Java+Spring Boot + Spring Security + JWT
- 前端:Ant Design Pro
二、Web登錄安全隱患
如果你的后臺管理系統(tǒng)登錄情況和下圖的情況一樣,登錄時用戶名、密碼是明文傳輸?shù)?,那么你是時候考慮要用非對稱性加密來保障Web登錄安全了。

三、HTTPS就一定安全嗎?
HTTPS存在兩種可能的風(fēng)險:
1、HTTPS可以保證傳輸過程中的信息不被別人截獲,但是細(xì)細(xì)思考下,HTTPS是應(yīng)用層協(xié)議,下層采用SSL保證信息安全,但是在客戶端和服務(wù)端,密文同樣是可以被截獲的;
2、HTTPS報文在傳輸過程中,如果客戶端被惡意引導(dǎo)安裝“中間人”的WEB信任證書,那么HTTPS中的“中間人攻擊”一樣會將明文密碼泄露給別人。
結(jié)論是:無論HTTP還是HTTPS,密碼必須密文傳輸。
四、采用什么方式才能保障安全
4.1 Base64轉(zhuǎn)碼可以嗎?
有同學(xué)可能會考慮,現(xiàn)在登錄名、密碼是明文不安全,那我將用戶名、密碼機(jī)進(jìn)行Base64轉(zhuǎn)碼別人不就看不懂了嗎?這個同學(xué)有點天真有點可愛,因為Base64完全沒有安全性可言,你能用Base64轉(zhuǎn)碼,別人就能用Base64解碼,而且不用寫代碼,百度一下Base64在線解碼就可以了,所以這種方案不可行。
4.2 對稱加密可以嗎?
答案仍然是:不行。稍微有點技術(shù)能力的人,只要利用相關(guān)工具或手段,就能看到你前端所采用的對稱加密算法和密鑰,分分鐘破解,所以這種方式仍然不夠安全。
4.3 MD5可以嗎?
有同學(xué)考慮用MD5方案的原因大概是認(rèn)為MD5是不可逆的,所以轉(zhuǎn)碼后,即使“黑客”拿到了密文那也是是非常難破解的(前提是密碼不是常用密碼,如:admin、123456這種,不然就有可能被暴力破解或者密碼庫進(jìn)行破解)。問題是,你前端MD5轉(zhuǎn)碼后傳到后端,后端無法進(jìn)行解碼,所以也不認(rèn)識你傳的是個什么鬼,不能進(jìn)行下一步的相關(guān)邏輯處理,所以這種方案仍然不可行。
五、非對稱性加密可以很好地解決這個問題
對于還不了解什么是非對稱性加密的同學(xué),可以先看看非對稱性加密的基礎(chǔ)知識點。
5.1 非對稱性加密定義
1976年,美國學(xué)者Dime和Henman為解決信息公開傳送和密鑰管理問題,提出一種新的密鑰交換協(xié)議,允許在不安全的媒體上的通訊雙方交換信息,安全地達(dá)成一致的密鑰,這就是“公開密鑰系統(tǒng)”。
5.2 非對稱性加密簡介
- 非對稱加密算法又稱現(xiàn)代加密算法。
- 非對稱加密是計算機(jī)通信安全的基石,保證了加密數(shù)據(jù)不會被破解。
- 與對稱加密算法不同,非對稱加密算法需要兩個密鑰:公開密鑰(publickey) 和私有密(privatekey)
- 公開密鑰和私有密鑰是一對
- 如果用公開密鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的私有密鑰才能解密。
- 如果用私有密鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的公開密鑰才能解密。
- 因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
即:
1 、A與B要通信,A和B都要產(chǎn)生一對用于加密和解密的公鑰和私鑰
2、A的私鑰保密,A的公鑰告訴B;B的私鑰保密,B的公鑰告訴A。
3、A要給B發(fā)送信息時,A用B的公鑰加密信息,因為A知道B的公鑰。
4、A將這個消息發(fā)給B(已經(jīng)用B的公鑰加密消息)。
5、B收到這個消息后,B用自己的私鑰解密A的消息。其他所有收到這個報文的人都無法解密,因為只有B才有B的私鑰。
5.3 非對稱加密經(jīng)典算法
RSA:可用于加密和簽名
DSA:僅用于簽名,但速度更快
5.4 特點
算法強(qiáng)度復(fù)雜,安全性依賴于算法與密鑰。
5.5缺點
由于其算法復(fù)雜,而使得加密解密速度沒有對稱加密解密的速度快。了解完非對稱性加密的內(nèi)容后,我們可以發(fā)現(xiàn),用非對稱性加密就能很好的解決Web登錄用戶名、密碼的安全性問題。
六、采用RSA非對稱性加密算法加密用戶名、密碼
話不多說,直接上代碼
6.1 后端Java相關(guān)代碼
/**
* @author Alan Chen
* @description
* @date 2019-11-23
*/
public class RsaKey {
//公鑰
private String publicKey;
//私鑰
private String privateKey;
}
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author Alan Chen
* @description 公鑰加密-私鑰解密 ; 私鑰加密-公鑰解密
* @date 2019-11-23
*/
public class RsaUtil {
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
/**
* 創(chuàng)建RSA 公鑰-私鑰
* @return
*/
public static RsaKey createKeys(){
return createKeys(1024);
}
/**
* 創(chuàng)建RSA 公鑰-私鑰
* @param keySize
* @return
*/
public static RsaKey createKeys(int keySize){
//為RSA算法創(chuàng)建一個KeyPairGenerator對象
KeyPairGenerator kpg = null;
try{
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
//初始化KeyPairGenerator對象,密鑰長度
kpg.initialize(keySize);
//生成密匙對
KeyPair keyPair = kpg.generateKeyPair();
//得到公鑰
Key publicKey = keyPair.getPublic();
String publicKeyStr = new String(Base64.encodeBase64(publicKey.getEncoded()));
//得到私鑰
Key privateKey = keyPair.getPrivate();
String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));
RsaKey rsaKey = new RsaKey();
rsaKey.setPublicKey(publicKeyStr);
rsaKey.setPrivateKey(privateKeyStr);
return rsaKey;
}
/**
* 公鑰加密
* @param originalText 原文
* @param publicKey
* @return
*/
public static String publicEncrypt(String originalText, String publicKey){
RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
return publicEncrypt(originalText,rsaPublicKey);
}
/**
* 公鑰解密
* @param cipherText
* @param publicKey
* @return
*/
public static String publicDecrypt(String cipherText, String publicKey){
RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
return publicDecrypt(cipherText,rsaPublicKey);
}
/**
* 私鑰加密
* @param originalText
* @param privateKey
* @return
*/
public static String privateEncrypt(String originalText, String privateKey){
RSAPrivateKey rsaPrivateKey= getPrivateKey(privateKey);
return privateEncrypt(originalText,rsaPrivateKey);
}
/**
* 私鑰解密
* @param cipherText 密文
* @param privateKey
* @return
*/
public static String privateDecrypt(String cipherText, String privateKey){
RSAPrivateKey rsaPrivateKey= getPrivateKey(privateKey);
return privateDecrypt(cipherText,rsaPrivateKey);
}
/**
* 得到公鑰
* @param publicKey 密鑰字符串(經(jīng)過base64編碼)
* @throws Exception
*/
private static RSAPublicKey getPublicKey(String publicKey) {
//通過X509編碼的Key指令獲得公鑰對象
KeyFactory keyFactory = null;
try {
keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = null;
try {
key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return key;
}
/**
* 公鑰加密
* @param originalText
* @param publicKey
* @return
*/
private static String publicEncrypt(String originalText, RSAPublicKey publicKey){
try{
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), publicKey.getModulus().bitLength()));
}catch(Exception e){
throw new RuntimeException("加密字符串[" + originalText + "]時遇到異常", e);
}
}
/**
* 得到私鑰
* @param privateKey 密鑰字符串(經(jīng)過base64編碼)
* @throws Exception
*/
private static RSAPrivateKey getPrivateKey(String privateKey){
//通過PKCS#8編碼的Key指令獲得私鑰對象
KeyFactory keyFactory = null;
try {
keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = null;
try {
key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return key;
}
/**
* 私鑰解密
* @param cipherText
* @param privateKey
* @return
*/
private static String privateDecrypt(String cipherText, RSAPrivateKey privateKey){
try{
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), privateKey.getModulus().bitLength()), CHARSET);
}catch(Exception e){
throw new RuntimeException("解密字符串[" + cipherText + "]時遇到異常", e);
}
}
private static String privateEncrypt(String originalText, RSAPrivateKey privateKey){
try{
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, originalText.getBytes(CHARSET), privateKey.getModulus().bitLength()));
}catch(Exception e){
throw new RuntimeException("加密字符串[" + originalText + "]時遇到異常", e);
}
}
private static String publicDecrypt(String cipherText, RSAPublicKey publicKey){
try{
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), publicKey.getModulus().bitLength()), CHARSET);
}catch(Exception e){
throw new RuntimeException("解密字符串[" + cipherText + "]時遇到異常", e);
}
}
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize){
int maxBlock = 0;
if(opmode == Cipher.DECRYPT_MODE){
maxBlock = keySize / 8;
}else{
maxBlock = keySize / 8 - 11;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
int i = 0;
try{
while(datas.length > offSet){
if(datas.length-offSet > maxBlock){
buff = cipher.doFinal(datas, offSet, maxBlock);
}else{
buff = cipher.doFinal(datas, offSet, datas.length-offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
}catch(Exception e){
throw new RuntimeException("加解密閥值為["+maxBlock+"]的數(shù)據(jù)時發(fā)生異常", e);
}
byte[] resultDatas = out.toByteArray();
IOUtils.closeQuietly(out);
return resultDatas;
}
}
/**
* @author Alan Chen
* @description
* @date 2019-11-23
*/
public class CreateRsaKey {
public static void main (String[] args){
RsaKey rsaKey = RsaUtil.createKeys();
System.out.println("公鑰" );
System.out.println(rsaKey.getPublicKey());
System.out.println("私鑰");
System.out.println(rsaKey.getPrivateKey());
String str = "alanchen";
System.out.println("明文:" + str);
System.out.println("公鑰加密——私鑰解密");
//公鑰加密
String encodedData = RsaUtil.publicEncrypt(str,rsaKey.getPublicKey());
System.out.println("公鑰加密后密文:" + encodedData);
//私鑰解密
String decodedData = RsaUtil.privateDecrypt(encodedData, rsaKey.getPrivateKey());
System.out.println("私鑰解密后文字: " + decodedData);
}
}
在你之前的授權(quán)代碼中將前端傳過來的密文用你的私鑰進(jìn)行解密,代碼大概是這樣
username = RsaUtil.privateDecrypt(username,"你的私鑰");
password = RsaUtil.privateDecrypt(password,"你的私鑰");

6.2 前端React相關(guān)代碼
導(dǎo)入jsencrypt庫
import JSEncrypt from 'jsencrypt';
配置公鑰
//后端生成RSA公鑰-私鑰對后給出公鑰
const loginRsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYEUuQvo6GHyAid3lVlAG/CIoj9OwljFaNHP5OjBwXxHvucRxVVhgrO2UarRGmLWGH145IkWgMVRnT4/n4UwfbnxHPdrGn0kc7OlpuCQlUlccRByINSB9jcX5QYemswrqHxr+jyMiqrjtioftfjksy2uNhYNBMi82hjgo6o7G/swIDAQAB"
對用戶名、密碼用公鑰進(jìn)行加密
/**
* Spring Security授權(quán)
* @param {*} params
*/
export async function authority(params) {
const { username, password } = params;
var jsencrypt = new JSEncrypt();
jsencrypt.setPublicKey(loginRsaPublicKey);
var cipherPassword = jsencrypt.encrypt(password);
cipherPassword = cipherPassword.replace(/\+/g,'%2B'); //對+號進(jìn)行轉(zhuǎn)義字符,不然傳輸?shù)胶蠖藭兂煽崭?
var cipherUsername = jsencrypt.encrypt(username);
cipherUsername = cipherUsername.replace(/\+/g,'%2B'); //對+號進(jìn)行轉(zhuǎn)義字符,不然傳輸?shù)胶蠖藭兂煽崭?
return request(`${baseServerURL}/login?username=${cipherUsername}&&password=${cipherPassword}`, {
method: 'POST'
});
}
前端代碼大概是這樣

參考資料/推薦閱讀