实现思路
由RSA随机生成一对公钥和私钥,公钥方到客户端,私钥放到服务端,发送数据的时候由公钥对传输数据进行加密,然后发送给服务端,服务端用私钥才能对数据进行解密.下面是代码实现的例子
前端
jsencrypt.js 保存到项目中,并引入,提交明文密码之前,调用此方法加密
// 加密
function getSignString(data) {
var encrypt = new JSEncrypt();
// 公匙,由后端返回到前台
var pubKey = parent.publicKey;
encrypt.setPublicKey(pubKey);
var encodePassword = encrypt.encrypt(data);
return encodePassword;
}
后台传输公钥(此处前后端不分离,采用thymeleaf渲染)
<script th:inline="javascript">
parent.publicKey = [[${#servletContext.getAttribute('publicKey')}]];
</script>
后端
从配置文件读取预先准备好的公钥,存入servlet上下文中
package com.test.framework.startup;
import com.test.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
/** 系统启动往 servlet 上下文塞入数据
* ClassName AppStartup
* Description
*
* @author wang
* Date 2021/3/22 11:28
*/
@Order(1)
@Component
public class AppStartup implements ApplicationRunner, ServletContextAware {
private static final Logger log = LoggerFactory.getLogger(AppStartup.class);
@Value("${test.kkFileUrl}")
private String kkFileUrl;
@Value("${test.ip}")
private String ip;
@Value("${server.port}")
private String port;
@Value("${rsa.public.key}")
private String publicKey;
private ServletContext servletContext;
@Override
public void run(ApplicationArguments args) {
log.info("initialization ...");
servletContext.setAttribute(Constants.KK_FILE_URL, kkFileUrl);
servletContext.setAttribute(Constants.SERVER_URL, ip + ":" + port);
servletContext.setAttribute(Constants.PUBLIC_KEY, publicKey);
log.info("OK, completed");
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
工具类
package com.test.common.utils;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
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;
import java.util.HashMap;
import java.util.Map;
/**
* 实现的思路:
* 由RSA随机生成一对公钥和私钥,公钥方到客户端,私钥放到服务端,
* 发送数据的时候由公钥对传输数据进行加密,然后发送给服务端,服务端用私钥才能对数据进行解密.下面是代码实现的例子
*
* ClassName RSAUtils
* Description
*
* @author wang
* Date 2021/7/7 10:41
*/
public class RSAUtils {
private static Logger logger = LoggerFactory.getLogger(RSAUtils.class);
/**
* 用于封装随机产生的公钥与私钥
* @param
* @return
*/
private static Map<Integer, String> keyMap = new HashMap<>();
/**
* 测试方法
*
* @param args
* @return void
* <p>
* <p>
* 前端用crypto-js进行加密,
* npm i jsencrypt,
* 然后页面头引入import JSEncrypt from 'jsencrypt';
* const encrypt = new JSEncrypt();
* encrypt.setPublicKey('你的公钥');
* password = encrypt.encrypt(‘你的密码’);// 加密后的字符串
*/
public static void main(String[] args) {
//生成公钥和私钥
genKeyPair();
//加密字符串
String message = "df723820";
System.out.println("随机生成的公钥为:" + keyMap.get(0));
System.out.println("随机生成的私钥为:" + keyMap.get(1));
String messageEn = encrypt(message, keyMap.get(0));
System.out.println("加密后的字符串为:" + messageEn);
String messageDe = decrypt(messageEn, keyMap.get(1));
System.out.println("还原后的字符串为:" + messageDe);
}
/**
* 随机生成密钥对
*
* @param
* @return void
*/
public static void genKeyPair() {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
logger.info(e.getMessage());
}
// 初始化密钥对生成器,密钥大小为96-1024位
assert keyPairGen != null;
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
keyMap.put(0, publicKeyString); //0表示公钥
keyMap.put(1, privateKeyString); //1表示私钥
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
*/
public static String encrypt(String str, String publicKey) {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = null;
String outStr = null;
try {
pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
logger.info(e.getMessage());
}
//RSA加密
return outStr;
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
*/
public static String decrypt(String str, String privateKey) {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = null;
//RSA解密
Cipher cipher = null;
String outStr = null;
try {
priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
e.printStackTrace();
logger.info(e.getMessage());
}
return outStr;
}
}
登陆控制器
package com.test.project.security.system.user.controller;
import com.alibaba.fastjson.JSONObject;
import com.test.common.utils.RSAUtils;
import com.test.common.utils.ServletUtils;
import com.test.common.utils.StringUtils;
import com.test.common.utils.security.ShiroUtils;
import com.test.framework.web.controller.BaseController;
import com.test.framework.web.domain.AjaxResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 登录验证
*
* @author test
*/
@Controller
public class LoginController extends BaseController
{
@Value("${rsa.private.key}")
private String privateKey;
@PostMapping("/login")
@ResponseBody
public AjaxResult ajaxLogin(HttpSession session, String username, String password, Boolean rememberMe)
{
// 使用秘钥将前端加密密码解密
password = RSAUtils.decrypt(password, privateKey);
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try
{
subject.login(token);
session.setAttribute("user", JSONObject.toJSONString(ShiroUtils.getSysUser()));
return AjaxResult.success("操作成功", token);
}
catch (AuthenticationException e)
{
String msg = "用户或密码错误";
if (StringUtils.isNotEmpty(e.getMessage()))
{
msg = e.getMessage();
}
return error(msg);
}
}
}
参考链接:添加链接描述
参考链接:添加链接描述