[Spring Cloud] (5)gateway前后端公私钥与认证信息

embedded/2024/10/19 9:33:27/

文章目录

  • 简述
  • 后端
    • pom
      • 增加hutool工具类
    • nacos
      • 增加登录过期时间配置
      • 修改全局配置文件
    • 安全通信认证接口
      • 控制层
      • 接口层
      • 实现层
    • 工具类
      • AES 对称加密工具类
      • MD5工具类
      • RSA非对称加密工具类
      • 加密盐工具类
  • 前端
    • 引入jsencrypt
    • 工具类
      • securityUtils.js
    • 请求类
      • 系统通信密钥接口
    • 登录接口增加认证数据接口

简述

本文gateway,微服务,vue已开源到gitee
杉极简/gateway网关阶段学习
实现目的:
前端请求后端接口获得到服务端公钥。

http://localhost:51001/k

得到服务端公钥后,客户端生成自己的公钥与私钥,并将自己的公钥加密发送给服务端。
再次请求接口得到认证信息。

http://localhost:51001/cn

前端得到以下认证信息,之后将基于这些认证信息进行安全通信。

    @ApiModelProperty("加密密钥")private String secretKey;@ApiModelProperty("会话id")private String sessionId;@ApiModelProperty("盐")private String salt;@ApiModelProperty("服务端通信公钥")private String publicKey;@ApiModelProperty("客户端端通信公钥")private String clientPublicKey;

将两个接口融入到登录接口当中去
点击登录后,先请求http://localhost:51001/k与http://localhost:51001/cn接口,之后在进行登录操作,
image.png
image.png

image.png
后端也根据sessionId存储认证信息
image.png

后端

pom

增加hutool工具类

        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.20</version></dependency>

nacos

增加登录过期时间配置

新增connectExpirationTime字段

global:# 全局异常捕捉-打印堆栈异常printStackTrace: true# 令牌头变量名称tokenHeader: Authorization# 令牌校验tokenCheck: true# 通信过期时间()connectExpirationTime: 1800# 不需要进行过滤的白名单whiteUrls:- /tick/auth/login- /ws

修改全局配置文件

随之修改全局配置文件

package com.fir.gateway.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.TimeUnit;/*** @author fir* @date 2023/7/28 17:53*/
@Data
@Component
@ConfigurationProperties(prefix = "global")
public class GlobalConfig {/*** 全局异常捕捉-打印堆栈异常*/private boolean printStackTrace;/*** 令牌头变量名称*/private String tokenHeader;/*** 令牌校验*/private boolean tokenCheck;/*** 登录过期时间*/private Integer loginExpirationTime;/*** 设置每次登录的过期时间单位(秒)*/private TimeUnit loginExpirationTimeUNIT = TimeUnit.SECONDS;/*** 白名单路由-不进行网关校验直接放过*/private List<String> whiteUrls;}

安全通信认证接口

后端增加接口,用于服务器与客户端交换公钥等。

控制层

package com.fir.gateway.controller;import com.fir.gateway.config.result.AjaxResult;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.service.IAuthService;
import com.fir.gateway.utils.RSAUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** 系统登录验证** @author dpe* @date 2022/8/4 22:58*/
@Api(tags = "系统登录接口")
@Slf4j
@RestController
@RefreshScope
public class AuthController {/*** 系统登录验证 接口层*/@Resourceprivate IAuthService iAuthService;@ApiOperation("客户端与服务端建立连接")@RequestMapping("/k")public AjaxResult info() {String publicKey = iAuthService.getPublicKey();return AjaxResult.success(publicKey);}@ApiOperation("客户端与服务端建立连接")@ApiImplicitParams({@ApiImplicitParam(name = "k", value = "公钥字符串"),@ApiImplicitParam(name = "k", value = "客户端公钥字符串"),})@RequestMapping("/cn")public AjaxResult connect(String k, String ck) {ConnectDTO connectDTO = iAuthService.info(k, ck);String content = RSAUtils.encryptSection(connectDTO, connectDTO.getClientPublicKey());return AjaxResult.success(content);}
}

接口层

package com.fir.gateway.service;import com.fir.gateway.dto.ConnectDTO;/*** @author fir* @date 2023/4/23 17:03*/
public interface IAuthService {/*** 获取公钥** @return 公钥*/String getPublicKey();/*** 获取通信认证信息** @param publicKeyMd5    RSA公钥md5取值* @param clientPublicKey 客户端公钥* @return 认证信息*/ConnectDTO info(String publicKeyMd5, String clientPublicKey);
}

实现层

package com.fir.gateway.service.impl;import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.dto.RsaKeyDTO;
import com.fir.gateway.service.IAuthService;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.MD5Utils;
import com.fir.gateway.utils.RSAUtils;
import com.fir.gateway.utils.SaltedHashUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;/*** @author fir* @date 2023/4/23 17:03*/
@Slf4j
@Service
public class AuthServiceImpl implements IAuthService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;/*** 获取公钥** @return 公钥*/@Overridepublic String getPublicKey() {// 生成本此次连接的公钥与私钥对存储,将公钥发送到前端作为加密通信签名的方式Map<String, String> map = RSAUtils.generateKey();String publicKey = map.get(RSAUtils.PUBLIC_KEY);String privateKey = map.get(RSAUtils.PRIVATE_KEY);// 将公钥取md5后,作为key存入redis中String publicKeyMd5 = MD5Utils.generateMd5ForString(publicKey);RsaKeyDTO rsaKeyDTO = new RsaKeyDTO();rsaKeyDTO.setPublicKey(publicKey);rsaKeyDTO.setPrivateKey(privateKey);Object obj = JSONObject.toJSON(rsaKeyDTO);redisTemplate.opsForValue().set(publicKeyMd5, obj,globalConfig.getConnectExpirationTime(), globalConfig.getConnectExpirationTimeUNIT());return publicKey;}/*** 获取通信认证信息** @param publicKeyMd5       RSA公钥* @param clientPublicKey 客户端公钥* @return 认证信息*/@Overridepublic ConnectDTO info(String publicKeyMd5, String clientPublicKey) {ConnectDTO connectDTO;// 将公钥取md5后,作为key存入redis中JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(publicKeyMd5);if (jsonObject != null) {RsaKeyDTO rsaKeyDTO = jsonObject.toJavaObject(RsaKeyDTO.class);String publicKey = rsaKeyDTO.getPublicKey();String privateKey = rsaKeyDTO.getPrivateKey();clientPublicKey = RSAUtils.decryptSection(clientPublicKey, privateKey);String secretKey = AESUtils.generateKeyAES();String sessionId = generateSessionId();String salt = SaltedHashUtils.generateSalt();connectDTO = ConnectDTO.builder().secretKey(secretKey).sessionId(sessionId).salt(salt).privateKey(privateKey).clientPublicKey(clientPublicKey).build();redisTemplate.opsForValue().set(sessionId, JSONObject.toJSON(connectDTO),globalConfig.getConnectExpirationTime(), globalConfig.getConnectExpirationTimeUNIT());connectDTO = ConnectDTO.builder().secretKey(secretKey).sessionId(sessionId).salt(salt).publicKey(publicKey).clientPublicKey(clientPublicKey).build();return connectDTO;} else {throw new CustomException(AjaxStatus.FAILED_COMMUNICATION);}}private static final SecureRandom RANDOM = new SecureRandom();public static String generateSessionId() {byte[] bytes = new byte[32];RANDOM.nextBytes(bytes);return Base64.getEncoder().encodeToString(bytes);}}

工具类

AES 对称加密工具类

package com.fir.gateway.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;/*** 功能:AES 工具类* 说明:对称分组密码算法* @author fir* @date 2020-5-20 11:25*/
@Slf4j
@SuppressWarnings("all")
public class AESUtils {private static final Logger logger = LoggerFactory.getLogger(AESUtils.class);public final static String KEY_ALGORITHMS = "AES";public final static int KEY_SIZE = 128;/*** 生成AES密钥,base64编码格式 (128)* @return* @throws Exception*/public static String getKeyAES_128() throws Exception{KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHMS);keyGen.init(KEY_SIZE);SecretKey key = keyGen.generateKey();String base64str = Base64.encodeBase64String(key.getEncoded());return base64str;}/*** 生成AES密钥,base64编码格式 (256)* @return* @throws Exception*/public static String getKeyAES_256() throws Exception{// 256需要换jar包暂时用128String base64str = getKeyAES_128();return base64str;}/*** 根据base64Key获取SecretKey对象* @param base64Key* @return*/public static SecretKey loadKeyAES(String base64Key) {byte[] bytes = Base64.decodeBase64(base64Key);SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, KEY_ALGORITHMS);return secretKeySpec;}/*** 生成SecretKey, base64对象** @return*/public static String generateKeyAES() {String keyBase64Str = null;String base64Key = "";try {base64Key = AESUtils.getKeyAES_128();} catch (Exception e) {e.printStackTrace();log.error("AES密钥生成失败");}if(!base64Key.equals("")){byte[] bytes = Base64.decodeBase64(base64Key);SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, KEY_ALGORITHMS);keyBase64Str = Base64.encodeBase64String(secretKeySpec.getEncoded());}return keyBase64Str;}/*** AES 加密字符串,SecretKey对象** @param encryptData* @param key* @param encode* @return*/public static String encrypt(String encryptData, SecretKey key, String encode) {try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.ENCRYPT_MODE, key);byte[] encryptBytes = encryptData.getBytes(encode);byte[] result = cipher.doFinal(encryptBytes);return Base64.encodeBase64String(result);} catch (Exception e) {logger.error("加密异常:" + e.getMessage());return null;}}/*** AES 加密字符串,base64Key对象** @param encryptData* @param base64Key* @param encode* @return*/public static String encrypt(String encryptData, String base64Key, String encode) {SecretKey key = loadKeyAES(base64Key);try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.ENCRYPT_MODE, key);byte[] encryptBytes = encryptData.getBytes(encode);byte[] result = cipher.doFinal(encryptBytes);return Base64.encodeBase64String(result);} catch (Exception e) {logger.error("加密异常:" + e.getMessage());return null;}}/*** AES 加密字符串,base64Key对象** @param encryptData* @param base64Key* @return*/public static String encrypt(String encryptData, String base64Key) {SecretKey key = loadKeyAES(base64Key);try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.ENCRYPT_MODE, key);byte[] encryptBytes = encryptData.getBytes(String.valueOf(StandardCharsets.UTF_8));byte[] result = cipher.doFinal(encryptBytes);return Base64.encodeBase64String(result);} catch (Exception e) {logger.error("加密异常:" + e.getMessage());return null;}}/*** AES 解密字符串,SecretKey对象** @param decryptData* @param key* @param encode* @return*/public static String decrypt(String decryptData, SecretKey key, String encode) {try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.DECRYPT_MODE, key);byte[] decryptBytes = Base64.decodeBase64(decryptData);byte[] result = cipher.doFinal(decryptBytes);return new String(result, encode);} catch (Exception e) {logger.error("加密异常:" + e.getMessage());return null;}}/*** AES 解密字符串,base64Key对象** @param decryptData* @param base64Key* @param encode* @return*/public static String decrypt(String decryptData, String base64Key, String encode) {SecretKey key = loadKeyAES(base64Key);try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.DECRYPT_MODE, key);byte[] decryptBytes = Base64.decodeBase64(decryptData);byte[] result = cipher.doFinal(decryptBytes);return new String(result, encode);} catch (Exception e) {logger.error("加密异常:" + e.getMessage());return null;}}/*** AES 解密字符串,base64Key对象** @param decryptData* @param base64Key* @return*/public static String decrypt(String decryptData, String base64Key) {SecretKey key = loadKeyAES(base64Key);try {final Cipher cipher = Cipher.getInstance(KEY_ALGORITHMS);cipher.init(Cipher.DECRYPT_MODE, key);byte[] decryptBytes = Base64.decodeBase64(decryptData);byte[] result = cipher.doFinal(decryptBytes);return new String(result, String.valueOf(StandardCharsets.UTF_8));} catch (Exception e) {logger.error("解密异常:" + e.getMessage());return null;}}
}

MD5工具类

package com.fir.gateway.utils;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;/*** @author fir*/
public class MD5Utils {private static MessageDigest md;static {try {//初始化摘要对象md = MessageDigest.getInstance("md5");} catch (NoSuchAlgorithmException e) {e.printStackTrace();}}/*** 获得字符串的md5值** @param str 字符串* @return MD5值*/public static String generateMd5ForString(String str){//更新摘要数据md.update(str.getBytes());//生成摘要数组byte[] digest = md.digest();//清空摘要数据,以便下次使用md.reset();return formatByteArrayToString(digest);}/*** 获得文件的md5值** @param file 文件对象* @return 文件MD5值* @throws IOException 文件流读取异常*/public static String generateMd5ForFile(File file) throws IOException {//创建文件输入流FileInputStream fis = new FileInputStream(file);//将文件中的数据写入md对象byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {md.update(buffer, 0, len);}fis.close();//生成摘要数组byte[] digest = md.digest();//清空摘要数据,以便下次使用md.reset();return formatByteArrayToString(digest);}/*** 将摘要字节数组转换为md5值** @param digest 字节数组* @return MD5数值*/public static String formatByteArrayToString(byte[] digest) {//创建sb用于保存md5值StringBuilder sb = new StringBuilder();int temp;for (byte b : digest) {//将数据转化为0到255之间的数据temp = b & 0xff;if (temp < 16) {sb.append(0);}//Integer.toHexString(temp)将10进制数字转换为16进制sb.append(Integer.toHexString(temp));}return sb.toString();}
}

RSA非对称加密工具类

package com.fir.gateway.utils;import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Encoder;import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;/*** RSA非对称加密工具方法** @author fir*/
@Slf4j
public class RSAUtils {private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";/*** 类型*/public static final String ENCRYPT_TYPE = "RSA";/*** 公钥名称*/public static final String PUBLIC_KEY = "publicKey";/*** 私钥名称*/public static final String PRIVATE_KEY = "privateKey";/*** 生成公钥私钥对** @return 公钥私钥对*/public static Map<String, String> generateKey() {Map<String, String> map = new HashMap<>();KeyPair pair = SecureUtil.generateKeyPair(ENCRYPT_TYPE);PrivateKey privateKey = pair.getPrivate();PublicKey publicKey = pair.getPublic();String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());map.put(PUBLIC_KEY, publicKeyStr);map.put(PRIVATE_KEY, privateKeyStr);return map;}/*** 从文件中读取公钥** @param filename 公钥保存路径* @return 公钥字符串*/public static String getPublicKey(String filename) {//默认UTF-8编码,可以在构造中传入第二个参数做为编码FileReader fileReader = new FileReader(filename);return fileReader.readString();}/*** 从文件中读取密钥** @param filename 私钥保存路径* @return 私钥字符串*/public static String getPrivateKey(String filename) {//默认UTF-8编码,可以在构造中传入第二个参数做为编码FileReader fileReader = new FileReader(filename);return fileReader.readString();}/*** 公钥加密** @param content   要加密的内容* @param publicKey 公钥*/public static String encrypt(String content, PublicKey publicKey) {try {RSA rsa = new RSA(null, publicKey);return rsa.encryptBase64(content, KeyType.PublicKey);} catch (Exception e) {e.printStackTrace();}return null;}/*** 公钥加密** @param content   要加密的内容* @param publicKey 公钥(base64字符串)*/public static String encrypt(String content, String publicKey) {try {RSA rsa = new RSA(null, publicKey);return rsa.encryptBase64(content, KeyType.PublicKey);} catch (Exception e) {e.printStackTrace();}return null;}/*** 公钥加密-分段加密(解密时需要分段解密)** @param t   要加密的内容(泛型对象,转化为Json字符串加密)* @param publicKey 公钥(base64字符串)*/public static <T> String encryptSection(T t, String publicKey) {String content = JSONObject.toJSONString(t);try {return encryptSection(content, publicKey);} catch (Exception e) {e.printStackTrace();}return null;}/*** 公钥加密-分段加密(解密时需要分段解密)** @param content   要加密的内容* @param publicKey 公钥(base64字符串)*/public static String encryptSection(String content, String publicKey) {try {RSA rsa = new RSA(null, publicKey);int blockSize = 117;int encryptedLength = content.length();StringBuilder decryptedBlocks = new StringBuilder();// 拆分加密文本为块并逐个解密for (int i = 0; i < encryptedLength; i += blockSize) {int b = i + blockSize;if (b > encryptedLength) {b = encryptedLength;}String block = content.substring(i, b);String decryptedBlock = rsa.encryptBase64(block, KeyType.PublicKey);decryptedBlocks.append(decryptedBlock);}return decryptedBlocks.toString();} catch (Exception e) {e.printStackTrace();}return null;}/*** 私钥解密** @param content    要解密的内容* @param privateKey 私钥*/public static String decrypt(String content, PrivateKey privateKey) {try {RSA rsa = new RSA(privateKey, null);return rsa.decryptStr(content, KeyType.PrivateKey);} catch (Exception e) {e.printStackTrace();}return null;}/*** 私钥解密** @param content    要解密的内容* @param privateKey 私钥(base64字符串)*/public static String decrypt(String content, String privateKey) {try {RSA rsa = new RSA(privateKey, null);return rsa.decryptStr(content, KeyType.PrivateKey);} catch (Exception e) {e.printStackTrace();}return null;}/*** 私钥解密-(只能解密分段解密的数据)** @param content    要解密的内容 (只能解密分段解密的数据)* @param privateKey 私钥(base64字符串)*/public static String decryptSection(String content, String privateKey) {try {RSA rsa = new RSA(privateKey, null);int blockSize = 172;int encryptedLength = content.length();StringBuilder decryptedBlocks = new StringBuilder();// 拆分加密文本为块并逐个解密for (int i = 0; i < encryptedLength; i += blockSize) {int b = i + blockSize;if (b > encryptedLength) {b = encryptedLength;}String block = content.substring(i, b);String decryptedBlock = rsa.decryptStr(block, KeyType.PrivateKey);decryptedBlocks.append(decryptedBlock);}return decryptedBlocks.toString();} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取公私钥-请获取一次后保存公私钥使用** @param publicKeyFilename  公钥生成的路径* @param privateKeyFilename 私钥生成的路径*/public static void generateKeyPair(String publicKeyFilename, String privateKeyFilename) {try {KeyPair pair = SecureUtil.generateKeyPair(ENCRYPT_TYPE);PrivateKey privateKey = pair.getPrivate();PublicKey publicKey = pair.getPublic();// 获取 公钥和私钥 的 编码格式(通过该 编码格式 可以反过来 生成公钥和私钥对象)byte[] pubEncBytes = publicKey.getEncoded();byte[] priEncBytes = privateKey.getEncoded();// 把 公钥和私钥 的 编码格式 转换为 Base64文本 方便保存String pubEncBase64 = new BASE64Encoder().encode(pubEncBytes);String priEncBase64 = new BASE64Encoder().encode(priEncBytes);FileWriter pub = new FileWriter(publicKeyFilename);FileWriter pri = new FileWriter(privateKeyFilename);pub.write(pubEncBase64);pri.write(priEncBase64);} catch (Exception e) {e.printStackTrace();}}// - - - - - - - - - - - - - - - - - - - - SIGN 签名,验签 - - - - - - - - - - - - - - - - - - - - ///*** 加签:生成报文签名** @param content    报文内容* @param privateKey 私钥* @param encode     编码* @return 签名*/public static String doSign(String content, String privateKey, String encode) {try {String unSign = Base64.encodeBase64String(content.getBytes(StandardCharsets.UTF_8));byte[] privateKeys = Base64.decodeBase64(privateKey.getBytes());PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeys);KeyFactory mykeyFactory = KeyFactory.getInstance(ENCRYPT_TYPE);PrivateKey psbcPrivateKey = mykeyFactory.generatePrivate(privateKeySpec);Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);signature.initSign(psbcPrivateKey);signature.update(unSign.getBytes(encode));byte[] signed = signature.sign();return Base64.encodeBase64String(signed);} catch (Exception e) {log.error("生成报文签名出现异常");}return null;}/*** 验证:验证签名信息** @param content   签名报文* @param signed    签名信息* @param publicKey 公钥* @param encode    编码格式* @return 通过/失败*/public static boolean doCheck(String content, String signed, PublicKey publicKey, String encode) {try {// 解密之前先把content明文,进行base64转码String unsigned = Base64.encodeBase64String(content.getBytes(encode));Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);signature.initVerify(publicKey);signature.update(unsigned.getBytes(encode));return signature.verify(Base64.decodeBase64(signed));} catch (Exception e) {log.error("报文验证签名出现异常");}return false;}
}

加密盐工具类

package com.fir.gateway.utils;import org.springframework.security.crypto.bcrypt.BCrypt;import java.security.SecureRandom;/*** 盐加密函数函数** @author fir* @date 2023/7/13 21:19*/
public class SaltedHashUtils {/*** 盐的长度*/private static final int SALT_LENGTH = 16;/*** 盐值生成** @return 16位盐值*/public static String generateSalt() {SecureRandom secureRandom = new SecureRandom();byte[] salt = new byte[SALT_LENGTH];secureRandom.nextBytes(salt);return bytesToHex(salt);}/*** 密码加盐** @param password 密码* @param salt 盐值* @return 加盐数值*/public static String generateHash(String password, String salt) {String saltedPassword = salt + password;return BCrypt.hashpw(saltedPassword, BCrypt.gensalt());}/*** 密码,盐 对比 盐后数值 是否相同** @param password 密码* @param salt 盐值* @return 相同:true/不相同:false*/public static boolean validatePassword(String password, String salt, String hashedPassword) {String saltedPassword = salt + password;return BCrypt.checkpw(saltedPassword, hashedPassword);}/*** 字节转16进制** @param bytes 字节流* @return 字符串*/private static String bytesToHex(byte[] bytes) {StringBuilder result = new StringBuilder();for (byte b : bytes) {result.append(String.format("%02x", b));}return result.toString();}
}

前端

引入jsencrypt

npm install jsencrypt --save

工具类

securityUtils.js

修改securityUtils.js增加加解密公私钥生成的逻辑。

import crypto from "crypto";
import {JSEncrypt} from "jsencrypt";
const CryptoJS = require('crypto-js');/** 全局变量配置-start **/// url白名单设置
const whiteList = ["/tick/auth/login","/k","/cn",
]/** 全局变量配置-end **/export default {/*** 读取信息*/get(key) {return sessionStorage.getItem(key)},/*** 添加信息*/set(key, value) {sessionStorage.setItem(key, value)},/*** 登录之后进行处理*/loginDeal(token){this.set("token", token)},//************************************网关通信-start// 与后台网关建立连接,需要请求 “/k” 接口, 拿到后端的公钥,存储。// 再请求 “/cn” 接口,保存与后端建立通信所需要的请求。/*** 用于网关请求 “/k” 请求后的处理** @returns {{ck: (string|null), k: string}}*/dealValidationMessage(data) {this.set("publicKey", data)},/*** gateway网关验证信息处理(请求头)*/gatewayRequest(request) {let key = true;whiteList.find(function (value) {if (value === request.url) {key = false;}});// 对非白名单请求进行处理if (key) {// 请求体数据let token = this.get("token")// 请求中增加tokenif (token) {request.headers.Authorization = token;}}return request;},/*** 用于网关请求 “/cn” 请求前的处理** @returns {{ck: (string|null), k: string}}*/secureConnectionPrepare() {const publicKey = this.get("publicKey")const publicKeyMd5 = this.strToMd5(publicKey)let clientPublicKey = this.communication()clientPublicKey = this.rsaEncrypt(clientPublicKey, publicKey)return {"k": publicKeyMd5,"ck": clientPublicKey,};},/*** 用于网关请求 “/cn” 请求后的处理*/secureConnection(data) {const privateKey = this.get("privateKey")data = this.rsaDecrypt(data, privateKey)data = JSON.parse(data)this.set("secretKey", data.secretKey)this.set("sessionId", data.sessionId)this.set("serverPublicKey", data.publicKey)},//************************************网关通信-end/*** 生成公钥私钥对保存本地,并返回公钥** @returns {string}*/communication() {const keys = this.rsaGenerateKey();const publicKey = keys.publicKey;const privateKey = keys.privateKey;this.set("privateKey", privateKey)return publicKey},//************************************公用加密方法-start/*** 将字符串取值MD5** @param string 字符串对象* @returns {string} 字符串md5数值*/strToMd5(string) {// 规定使用哈希算法中的MD5算法const hash = crypto.createHash('md5');// 可任意多次调用update(),效果相当于多个字符串相加hash.update(string);// hash.digest('hex')表示输出的格式为16进制return hash.digest('hex');},//************************************公用加密方法-end//************************************AES对称加解密-start/*** AES对称加密数据** @param {String} data 待加密的数据* @param {String} base64Key base64格式的密钥* @returns {String} 加密后的数据*/encryptAES(data, base64Key) {let encryptedBytes = null;if (data != null && base64Key != null) {const key = CryptoJS.enc.Base64.parse(base64Key);encryptedBytes = CryptoJS.AES.encrypt(data, key, {mode: CryptoJS.mode.ECB});encryptedBytes = encryptedBytes.toString();}return encryptedBytes;},/*** AES对称-解密数据** @param {String} data 待解密的数据* @param {String} base64Key base64格式的密钥* @returns {String} 解密后的数据*/decryptAES(data, base64Key) {let decryptData = null;if (data != null && base64Key != null) {const key = CryptoJS.enc.Base64.parse(base64Key)const decryptBytes = CryptoJS.AES.decrypt(data, key, {mode: CryptoJS.mode.ECB})decryptData = CryptoJS.enc.Utf8.stringify(decryptBytes);}return decryptData},//************************************AES对称加解密-end//************************************RSA非对称加解密-start/*** 非对称加解密-生成公钥与私钥*/rsaGenerateKey() {let keys = {"publicKey": "","privateKey": "",}// 创建 JSEncrypt 实例const encrypt = new JSEncrypt();// 生成密钥对(公钥和私钥)const keyPair = encrypt.getKey();// 获取公钥和私钥keys.publicKey = keyPair.getPublicBaseKeyB64();keys.privateKey = keyPair.getPrivateBaseKeyB64();return keys},/*** 非对称加解密-公钥认证信息(分段加密)** @param string 内容* @param publicKey 非对称私钥* @returns {string | null}*/rsaEncrypt(string, publicKey) {let encryptData = null;if (string != null && publicKey != null) {const encryptor = new JSEncrypt();encryptor.setPublicKey(publicKey);// 根据公钥的长度确定块大小,一般为公钥长度减去一些填充长度const blockSize = 117;const textLength = string.length;let encryptedBlocks = [];// 拆分长文本为块并逐个加密for (let i = 0; i < textLength; i += blockSize) {const block = string.substr(i, blockSize);const encryptedBlock = encryptor.encrypt(block);encryptedBlocks.push(encryptedBlock);}// 将加密的块合并为单个字符串encryptData = encryptedBlocks.join('');}return encryptData;},/*** 非对称加解密-私钥解密信息(分段解密)** @param string 加密内容* @param privateKey 非对称私钥* @returns {string | null}*/rsaDecrypt(string, privateKey) {let decryptData = null;if (string != null && privateKey != null) {const encryptor = new JSEncrypt();encryptor.setPrivateKey(privateKey);// 根据私钥的长度确定块大小,一般为私钥长度减去一些填充长度const blockSize = 172;const encryptedLength = string.length;let decryptedBlocks = [];// 拆分加密文本为块并逐个解密for (let i = 0; i < encryptedLength; i += blockSize) {const block = string.substr(i, blockSize);const decryptedBlock = encryptor.decrypt(block);decryptedBlocks.push(decryptedBlock);}decryptData = decryptedBlocks.join('')}// 将解密的块合并为单个字符串return decryptData;},//************************************RSA非对称加解密-end
}

请求类

增加两个请求,用于访问后端的公钥数据与其他加密数据。

系统通信密钥接口

    /** 系统通信密钥 **/getPublicKey(obj) {return dataInterface("/k","get", obj)},/** 系统通信密钥 **/cn(obj) {return dataInterface("/cn","get", obj)},

登录接口增加认证数据接口

此时我们希望在登陆前,获取到与后端通信的公钥私钥以及其他的认证数据。

        /*** 获取与后端建立通信的必备信息*/async loginApi() {await this.connection()await this.$http.login(this.login).then(res => {let code = res.codelet msg = res.msglet data = res.datasecurityUtils.loginDeal(data.token)this.token = securityUtils.get("token")if (code === 200) {this.$message({message: msg, duration: 1.5, description: ''})} else {this.$message({message: "错误", duration: 1.5, description: ''})}})},
        /** 与后端建立联系 **/async connection() {// 获取后端RSA加密公钥await this.$http.getPublicKey().then(res => {let data = res.datasecurityUtils.dealValidationMessage(data)});// 获取与后端建立通信的必备信息let data = securityUtils.secureConnectionPrepare()await this.$http.cn(data).then(res => {let data = res.datasecurityUtils.secureConnection(data)})},

http://www.ppmy.cn/embedded/19015.html

相关文章

架构师系列- JVM(三)- 类加载

通过字节码&#xff0c;我们了解了class文件的结构 通过运行数据区&#xff0c;我们了解了jvm内部的内存划分及结构 接下来&#xff0c;让我们看看&#xff0c;字节码怎么进入jvm的内存空间&#xff0c;各自进入那个空间&#xff0c;以及怎么跑起来。 4.1 加载 4.1.1 概述 …

Qt tcp通信(客户端+服务器一对一)

学习自《Qt5.9 C开发指南》 服务器端&#xff1a; QTcpServer *tcpServer; //TCP服务器 tcpServernew QTcpServer(this); connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection())); 当有新的客户端接入时&#xff0c;QTcpServer内部的incomingConnectio…

Day41 HTTP编程

Day41 HTTP编程 文章目录 Day41 HTTP编程HTTP概念应用场景主要方面 HTTP案例案例一&#xff1a;获取淘宝商品周边类别案例二&#xff1a;下载图片 HTTP 概念 HTTP编程指的是使用HTTP协议进行网络编程的过程。HTTP是一种用于传输超文本的应用层协议&#xff0c;通常用于在客户…

QML与C++交互

Qt 你好 | 专注于Qt的技术分享平台 QML写界面&#xff0c;业务逻辑使用C&#xff0c;既能快速的开发界面也能利用C的强大生态&#xff0c;这是目前比较被认可的方式&#xff0c;那就涉及到QML与C对象的交互。 我们以登录例子来说明&#xff0c;页面点击登录&#xff0c;将信息…

区块链技术:NFG元宇宙电商模式

大家好&#xff0c;我是微三云周丽 随着互联网技术的迅猛发展&#xff0c;电子商务行业逐渐崛起为现代经济的重要支柱。而在这一浪潮中&#xff0c;元宇宙电商以其独特的商业模式和巨大的发展潜力&#xff0c;成为行业的新宠。其中&#xff0c;NFG作为元宇宙电商模式的代表&am…

鸿蒙(HarmonyOS)性能优化实战-多线程共享内存

概述 在应用开发中&#xff0c;为了避免主线程阻塞&#xff0c;提高应用性能&#xff0c;需要将一些耗时操作放在子线程中执行。此时&#xff0c;子线程就需要访问主线程中的数据。ArkTS采用了基于消息通信的Actor并发模型&#xff0c;具有内存隔离的特性&#xff0c;所以跨线…

FSMC读取FPGA的FIFO

一、硬件说明 FSMC配置 单片机的代码如下&#xff1a; #define VALUE_ADDRESS_AD1 (__IO uint16_t *)0x60400000while (1){if(!HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_8)) //数据非空{data *(__IO uint16_t *)VALUE_ADDRESS_AD1;data2 *(__IO uint16_t *)VALUE_ADDRESS_AD1…

RabbitMQ(高级)笔记

一、生产者可靠性 &#xff08;1&#xff09;生产者重连&#xff08;不建议使用&#xff09; logging:pattern:dateformat: MM-dd HH:mm:ss:SSSspring:rabbitmq:virtual-host: /hamllport: 5672host: 192.168.92.136username: hmallpassword: 123listener:simple:prefetch: 1c…