springboot+security基于前后端分离的RSA密码加密登录流程

📖摘要


今天分享下 —— springboot + security基于前后端分离的 RSA 密码加密登录流程,欢迎关注!

重点说明一下关于这个方式请求时可能会出现参数携带+号被置换成空格的骚操作,请移步这篇文章:RSA加密请求报错:javax.crypto.BadPaddingException: Decryption error


🌂RSA加密简介

RSA加密 是一种非对称加密。可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。


✨RSA加密

加密是为了安全性考虑,简单的说,加密是为了防止信息被泄露

场景:特工B要给特工A传递一条消息,内容为某一命令。

RSA的加密过程如下:
(1)A特工生成一对密钥(公钥和私钥),私钥不公开,A特工自己保留。公钥为公开的,任何人可以获取。
(2)A特工传递自己的公钥给B特工,B特工用A特工的公钥对消息进行加密。
(3)A特工接收到B特工加密的消息,利用A特工自己的私钥对消息进行解密。

在这个过程中,只有2次传递过程,第一次是A特工传递公钥给B特工,第二次是B特工传递加密消息给A特工,即使都被敌方截获,也没有危险性,因为只有A特工的私钥才能对消息进行解密,防止了消息内容的泄露。基于这一特性,我们可以在前后端登陆上进行密码加密。保证密码的安全性。

在这里插入图片描述


💖RSA密码加密(Java)实现

  • 登录功能,密码肯定不能以明文形式传输,所以前端传过来的密码就应该是RSA加密过后的密码。
  • 因为 RSA 是需要公钥和私钥的,公钥加密,私钥解密。那么就可以随机生成一个公钥私钥密钥对,然后将这个密钥对保存下来,不要泄露,将公钥给前端将密码加密,后端通过私钥解密。最终再使用加盐加密的方法将密码保存到数据库中
    下面是 RSA加密 的代码
  • 实际工作中可以将公钥和私钥提前生成好,然后放到配置文件中去
  • 说一下思路,具体用法可以直接运行 main 主函数慢慢研究。

Base64:封装base64编码。用于RSA密码编码解码

import com.fckj.fckjrestaurant.constant.Constant;
import com.fckj.fckjrestaurant.util.codec.Base64Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;


/**
 * @Description: RSA加解密
 * @BelongsProject: fckj-restaurant
 * @BelongsPackage: com.fckj.fckjrestaurant.util.RSA
 * @Author: ChenYongJia
 * @CreateTime: 2021-06-04 11:46
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Slf4j
public class RSAUtils {

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 获取密钥对
     *
     * @return java.security.KeyPair
     * @date 2021/6/7 15:32
     * @author ChenYongJia
     * @version 1.0
     */
    public static KeyPair getKeyPair() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance(Constant.ALGORITHM_NAME);
        generator.initialize(1024);
        return generator.generateKeyPair();
    }

    /**
     * 获取私钥
     *
     * @param privateKey 私钥字符串
     * @return java.security.PrivateKey
     * @date 2021/6/7 15:32
     * @author ChenYongJia
     * @version 1.0
     */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(Constant.ALGORITHM_NAME);
        byte[] decodedKey = Base64Utils.decoder(privateKey.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 获取公钥
     *
     * @param publicKey 公钥字符串
     * @param publicKey
     * @return java.security.PublicKey
     * @date 2021/6/7 15:32
     * @author ChenYongJia
     * @version 1.0
     */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(Constant.ALGORITHM_NAME);
        byte[] decodedKey = Base64Utils.decoder(publicKey.getBytes());
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * RSA加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return java.lang.String
     * @date 2021/6/7 15:32
     * @author ChenYongJia
     * @version 1.0
     */
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(Constant.ALGORITHM_NAME);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = data.getBytes().length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
        // 加密后的字符串
        return new String(Base64Utils.encoder(encryptedData));
    }

    /**
     * RSA解密
     *
     * @param data       待解密数据
     * @param privateKey 私钥
     * @return java.lang.String
     * @date 2021/6/7 15:33
     * @author ChenYongJia
     * @version 1.0
     */
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(Constant.ALGORITHM_NAME);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] dataBytes = Base64.decodeBase64(data);
        int inputLen = dataBytes.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        //对数据分段解密
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        // 解密后的内容
        return new String(decryptedData, "UTF-8");
    }

    /**
     * 签名
     *
     * @param data       待签名数据
     * @param privateKey 私钥
     * @return java.lang.String
     * @date 2021/6/7 15:33
     * @author ChenYongJia
     * @version 1.0
     */
    public static String sign(String data, PrivateKey privateKey) throws Exception {
        byte[] keyBytes = privateKey.getEncoded();
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(Constant.ALGORITHM_NAME);
        PrivateKey key = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance(Constant.MD5_RSA);
        signature.initSign(key);
        signature.update(data.getBytes());
        return new String(Base64Utils.encoder(signature.sign()));
    }

    /**
     * 验签
     *
     * @param srcData   原始字符串
     * @param publicKey 公钥
     * @param sign      签名
     * @return boolean
     * @date 2021/6/7 15:33
     * @author ChenYongJia
     * @version 1.0
     */
    public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
        byte[] keyBytes = publicKey.getEncoded();
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(Constant.ALGORITHM_NAME);
        PublicKey key = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(Constant.MD5_RSA);
        signature.initVerify(key);
        signature.update(srcData.getBytes());
        return signature.verify(Base64Utils.decoder(sign.getBytes()));
    }

    public static void main(String[] args) {
        try {
            // 生成密钥对
            KeyPair keyPair = getKeyPair();
            String privateKey = new String(Base64Utils.encoder(keyPair.getPrivate().getEncoded()));
            String publicKey = new String(Base64Utils.encoder(keyPair.getPublic().getEncoded()));
            log.info("私钥:" + privateKey);
            log.info("公钥:" + publicKey);
            // RSA加密
            String data = "123456";
            String encryptData = encrypt(data, getPublicKey(publicKey));
            log.info("加密后内容:" + encryptData);
            // RSA解密
            String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
            log.info("解密后内容:" + decryptData);
            // RSA签名
            String sign = sign(data, getPrivateKey(privateKey));
            // RSA验签
            boolean result = verify(data, getPublicKey(publicKey), sign);
            log.info("验签结果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("RSA加解密异常");
        }
    }

}


Springboot 业务代码实现

逻辑:前端获取 publickey——公钥,用公钥对密码进行编码加密,后端获取密码,用相对应的私钥解密加密数据,与数据库比对密码是否一致

controller 层就不写了,主要看 service 层业务逻辑代码:生成密钥对,放到 redis里面存储,返回前端公钥

 @Override
    public String getPublicKey() {
        try {
            Object privateKey = redisUtil.get(Constant.RSA_PRIVATE_KEY);
            Object publicKey = redisUtil.get(Constant.RSA_PUBLIC_KEY);
            if (WyCheckUtil.isEmpty(publicKey) || WyCheckUtil.isEmpty(privateKey)) {
                KeyPair keyPair = RSAUtils.getKeyPair();
                privateKey = new String(Base64Utils.encoder(keyPair.getPrivate().getEncoded()));
                publicKey = new String(Base64Utils.encoder(keyPair.getPublic().getEncoded()));
                // 存入私钥
                redisUtil.set(Constant.RSA_PRIVATE_KEY, privateKey);
                // 存入公钥
                redisUtil.set(Constant.RSA_PUBLIC_KEY, publicKey);
            }
            log.info("privateKey = {},publicKey = {}", privateKey, publicKey);
            return publicKey.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

前端:基于iview和vue框架

前端用crypto-js进行加密,
npm i jsencrypt,
然后页面头引入import JSEncrypt from 'jsencrypt';
const encrypt = new JSEncrypt();
encrypt.setPublicKey('你的公钥');
password = encrypt.encrypt(‘你的密码’);// 加密后的字符串
getPublicKey().then(res => {
            let password = this.form.password
            let publicKey = res.data.data.data
            console.log(publicKey)
            const encrypt = new JSEncrypt()
            encrypt.setPublicKey(publicKey)
            password = encrypt.encrypt(password)
            console.log(password)
            this.$emit('on-success-valid', {
              userName: this.form.userName,
              password: password
            })
          })

登录验证密码时解密对比

String inputDecryptData = "";
        try {
            Object privateKey = redisUtil.get(Constant.RSA_PRIVATE_KEY);
            inputDecryptData =  RSAUtils.decrypt(password, RSAUtils.getPrivateKey(privateKey.toString()));
        } catch (Exception e) {
            log.error("RSA加解密出现异常======>", e);
            throw new BizException("RSA加解密出现异常");
        }

先获取私钥,我是放到redis里面,然后用私钥解密加密数据,同事获取数据库用户对象,获取密码同样用私钥解密比对与用户输入的密码是否一致

至此,Springboot&security基于前后端分离的RSA密码加密登录流程就完成了

最后感谢大家耐心观看完毕,留个点赞收藏是您对我最大的鼓励!


🎉最后

  • 更多参考精彩博文请看这里:《陈永佳的博客》

  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈永佳

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值