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

 

 

一、RSA加密简介

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

  

二、RSA加密

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

场景:民国时期,B特工要给A特工传递一条消息,内容为某一指令。

RSA的加密过程如下:

(1)A特工生成一对密钥(公钥和私钥),私钥不公开,A特工自己保留。公钥为公开的,任何人可以获取。

(2)A特工传递自己的公钥给B特工,B特工用A特工的公钥对消息进行加密。

(3)A特工接收到B特工加密的消息,利用A特工自己的私钥对消息进行解密。

  在这个过程中,只有2次传递过程,第一次是A特工传递公钥给B特工,第二次是B特工传递加密消息给A特工,即使都被敌方截获,也没有危险性,因为只有A特工的私钥才能对消息进行解密,防止了消息内容的泄露。

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

基于这一特性,我们可以在前后端登陆上进行密码加密。保证密码的安全性。

三、RSA密码加密(Java)实现

package com.sparksys.mall.core.utils;

import java.util.Base64;

/**
 * 中文类名:base64加密工具类
 * 中文描述:base64加密工具类
 *
 * @author zhouxinlei
 * @date 2019-09-11 11:05:47
 */
public class Base64Utils {

    public static final Base64.Encoder encoder = Base64.getEncoder();
    public static final Base64.Decoder decoder = Base64.getDecoder();

    /**
     * base64加密
     *
     * @param encodeText 明文
     * @return
     */
    public static byte[] encoder(byte[] encodeText) {
        return encoder.encode(encodeText);
    }

    /**
     * base64加密
     *
     * @param decodeText 密文
     */
    public static byte[] decoder(byte[] decodeText) {
        return decoder.decode(decodeText);
    }

}

该工具封装base64编码。用于RSA密码编码解码

package com.sparksys.mall.core.utils;

import com.sparksys.mall.core.constant.AttributeConstant;
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;

/**
 * 中文类名: RSA密码加密工具类
 * 中文描述: RSA密码加密工具类
 *
 * @author zhouxinlei
 * @date 2019-09-11 10:30:43
 */
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
     * @author zhouxinlei
     * @date 2019-09-12 15:25:55
     */
    public static KeyPair getKeyPair() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance(AttributeConstant.ALGORITHM_NAME);
        generator.initialize(1024);
        return generator.generateKeyPair();
    }

    /**
     * 获取私钥
     *
     * @param privateKey 私钥字符串
     * @return java.security.PrivateKey
     * @author zhouxinlei
     * @date 2019-09-12 15:26:15
     */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(AttributeConstant.ALGORITHM_NAME);
        byte[] decodedKey = Base64Utils.decoder(privateKey.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 获取公钥
     *
     * @param publicKey 公钥字符串
     * @return java.security.PublicKey
     * @author zhouxinlei
     * @date 2019-09-12 15:26:40
     */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(AttributeConstant.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
     * @author zhouxinlei
     * @date 2019-09-12 15:27:07
     */
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(AttributeConstant.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
     * @author zhouxinlei
     * @date 2019-09-12 15:27:29
     */
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(AttributeConstant.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
     * @author zhouxinlei
     * @date 2019-09-12 15:24:08
     */
    public static String sign(String data, PrivateKey privateKey) throws Exception {
        byte[] keyBytes = privateKey.getEncoded();
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(AttributeConstant.ALGORITHM_NAME);
        PrivateKey key = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance(AttributeConstant.MD5_RSA);
        signature.initSign(key);
        signature.update(data.getBytes());
        return new String(Base64Utils.encoder(signature.sign()));
    }

    /**
     * 验签
     *
     * @param srcData   原始字符串
     * @param publicKey 公钥
     * @param sign      签名
     * @return boolean 是否验签通过
     * @author zhouxinlei
     * @date 2019-09-12 15:23:38
     */
    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(AttributeConstant.ALGORITHM_NAME);
        PublicKey key = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(AttributeConstant.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()));
            System.out.println("私钥:" + privateKey);
            System.out.println("公钥:" + publicKey);
            // RSA加密
            String data = "123456";
            String encryptData = encrypt(data, getPublicKey(publicKey));
            System.out.println("加密后内容:" + encryptData);
            // RSA解密
            String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
            System.out.println("解密后内容:" + decryptData);
            // RSA签名
            String sign = sign(data, getPrivateKey(privateKey));
            // RSA验签
            boolean result = verify(data, getPublicKey(publicKey), sign);
            System.out.print("验签结果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.print("加解密异常");
        }
    }
}

RSA密码加密解密,签名验证等而封装的工具类

四、Springboot业务代码实现

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

controller层就不写了,主要看service层业务逻辑代码:

@Override
    public ResultEntity getPublicKey() {
        try {
            String privateKey = redisUtil.get(AttributeConstant.RSA_PRIVATE_KEY);
            String publicKey = redisUtil.get(AttributeConstant.RSA_PUBLIC_KEY);
            if (StringUtils.isEmpty(publicKey) || StringUtils.isEmpty(privateKey)) {
                KeyPair keyPair = RSAUtils.getKeyPair();
                privateKey = new String(Base64Utils.encoder(keyPair.getPrivate().getEncoded()));
                publicKey = new String(Base64Utils.encoder(keyPair.getPublic().getEncoded()));
                redisUtil.set(AttributeConstant.RSA_PRIVATE_KEY, privateKey);
                redisUtil.set(AttributeConstant.RSA_PUBLIC_KEY, publicKey);
            }
            log.info("privateKey = {},publicKey = {}",privateKey,publicKey);
            return ResultEntity.success(publicKey, "获取公钥成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return ResultEntity.failed("获取公钥失败!");
        }
    }

 

注释:

String privateKey = redisUtil.get(AttributeConstant.RSA_PRIVATE_KEY);
String publicKey = redisUtil.get(AttributeConstant.RSA_PUBLIC_KEY);
if (StringUtils.isEmpty(publicKey) || StringUtils.isEmpty(privateKey)) {
    KeyPair keyPair = RSAUtils.getKeyPair();
    privateKey = new String(Base64Utils.encoder(keyPair.getPrivate().getEncoded()));
    publicKey = new String(Base64Utils.encoder(keyPair.getPublic().getEncoded()));
    redisUtil.set(AttributeConstant.RSA_PRIVATE_KEY, privateKey);
    redisUtil.set(AttributeConstant.RSA_PUBLIC_KEY, publicKey);
}

生成密钥对,放到redis里面存储,返回前端公钥

前端:基于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
            })
          })

登录service:

@Master
    @Override
    public ResultEntity login(UmsAdminLoginInDto umsAdminLoginInDto,
                              HttpServletResponse response) {
        try {
            String token;
            QueryWrapper<UmsAdmin> wrapper = new QueryWrapper();
            wrapper.eq("username", umsAdminLoginInDto.getUserName());
            UmsAdmin umsAdmin = adminDao.selectOne(wrapper);
            if (ObjectUtils.isEmpty(umsAdmin)) {
                return ResultEntity.failed("账户不存在!");
            }
            String privateKey = redisUtil.get(AttributeConstant.RSA_PRIVATE_KEY);
            String inputDecryptData = RSAUtils.decrypt(umsAdminLoginInDto.getPassword(),
                    RSAUtils.getPrivateKey(privateKey));
            String decryptData = RSAUtils.decrypt(umsAdmin.getPassword(),
                    RSAUtils.getPrivateKey(privateKey));
            if (!StringUtils.equals(decryptData, inputDecryptData)) {
                return ResultEntity.failed("密码错误!");
            }
            umsAdmin.setLoginTime(new Date());
            adminDao.updateById(umsAdmin);
            UserDetails userDetails =
                    userDetailsService.loadUserByUsername(umsAdminLoginInDto.getUserName());
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, null,
                            userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            //保存登录日志
            UmsAdminLoginLog umsAdminLoginLog = new UmsAdminLoginLog();
            umsAdminLoginLog.setAdminId(umsAdmin.getId());
            umsAdminLoginLog.setCreateTime(new Date());
            adminLoginLogDao.insert(umsAdminLoginLog);
            token = JwtTokenUtil.generateToken(userDetails);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("token", token);
            jsonObject.put("tokenHead", AttributeConstant.JWT_TOKEN_HEAD);
            response.setHeader(AttributeConstant.JWT_TOKEN_HEADER,
                    AttributeConstant.JWT_TOKEN_HEAD.concat(token));
            return ResultEntity.success(jsonObject, "登录成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return ResultEntity.failed("登录失败!");
        }
    }

注释:

String privateKey = redisUtil.get(AttributeConstant.RSA_PRIVATE_KEY);
String inputDecryptData = RSAUtils.decrypt(umsAdminLoginInDto.getPassword(),
        RSAUtils.getPrivateKey(privateKey));
String decryptData = RSAUtils.decrypt(umsAdmin.getPassword(),
        RSAUtils.getPrivateKey(privateKey));
if (!StringUtils.equals(decryptData, inputDecryptData)) {
    return ResultEntity.failed("密码错误!");
}

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

在此,Springboot&security基于前后端分离的RSA密码加密登录流程就完成了,感谢大家的观看,有问题可以@我

上一篇:理解 KingbaseES 中的递归查询


下一篇:KingbaseES 查询计划剖析