岚岚天空

前后端分离数据加密传输方案

226 0

前后端分离数据加密传输方案(Vue + Java)

推荐方案

对于Vue前端和Java后端的分离架构,推荐以下数据加密传输方案:

  1. HTTPS:基础安全层,必须启用

  2. AES对称加密:对敏感数据进行加密

  3. RSA非对称加密:用于传输AES密钥

  4. 数据签名:防止数据篡改

完整解决方案

1. 准备工作

  1. 确保服务器已配置HTTPS

  2. 后端生成RSA密钥对(公钥和私钥)

  3. 前端通过安全接口获取RSA公钥

2. 加密流程

  1. 前端生成随机AES密钥

  2. 使用RSA公钥加密AES密钥

  3. 使用AES密钥加密请求数据

  4. 将加密后的AES密钥和加密数据一起发送到后端

  5. 后端使用RSA私钥解密AES密钥,再用AES密钥解密数据

3. 前端实现(Vue示例)

首先安装加密库:

bash
复制
下载
npm install crypto-js jsencrypt

加密工具类 src/utils/crypto.js

javascript
复制
下载
import CryptoJS from 'crypto-js'import JSEncrypt from 'jsencrypt'// 存储RSA公钥let publicKey = ''// 设置RSA公钥export function setPublicKey(key) {
  publicKey = key}// 生成随机AES密钥export function generateAesKey() {
  const key = CryptoJS.lib.WordArray.random(32).toString()
  const iv = CryptoJS.lib.WordArray.random(16).toString()
  return { key, iv }}// RSA加密export function rsaEncrypt(data) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey)
  return encryptor.encrypt(data)}// AES加密export function aesEncrypt(data, key, iv) {
  const keyHex = CryptoJS.enc.Utf8.parse(key)
  const ivHex = CryptoJS.enc.Utf8.parse(iv)
  const encrypted = CryptoJS.AES.encrypt(data, keyHex, {
    iv: ivHex,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7  })
  return encrypted.toString()}// 封装加密请求数据export function encryptRequestData(data) {
  // 生成AES密钥
  const { key, iv } = generateAesKey()
  
  // 加密数据
  const encryptedData = aesEncrypt(JSON.stringify(data), key, iv)
  
  // 加密AES密钥
  const encryptedKey = rsaEncrypt(key)
  const encryptedIv = rsaEncrypt(iv)
  
  return {
    key: encryptedKey,
    iv: encryptedIv,
    data: encryptedData  }}

API请求封装 src/api/request.js

javascript
复制
下载
import axios from 'axios'import { encryptRequestData } from '@/utils/crypto'// 创建axios实例const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 10000})// 请求拦截器 - 加密数据service.interceptors.request.use(
  async config => {
    // 获取RSA公钥(可以从store或localStorage获取)
    const publicKey = localStorage.getItem('rsaPublicKey')
    
    // 如果是POST/PUT请求且需要加密
    if (['post', 'put'].includes(config.method) && config.needEncrypt) {
      const encryptedData = encryptRequestData(config.data)
      config.data = encryptedData    }
    
    return config  },
  error => {
    return Promise.reject(error)
  })// 响应拦截器 - 解密数据service.interceptors.response.use(
  response => {
    // 如果需要解密,可以在这里处理
    // const decryptedData = decryptResponseData(response.data)
    return response.data  },
  error => {
    return Promise.reject(error)
  })export default service

获取公钥并存储 src/api/auth.js

javascript
复制
下载
import request from './request'import { setPublicKey } from '@/utils/crypto'export function getPublicKey() {
  return request({
    url: '/auth/publicKey',
    method: 'get',
    needEncrypt: false
  }).then(response => {
    // 存储公钥
    setPublicKey(response.data)
    localStorage.setItem('rsaPublicKey', response.data)
    return response  })}

使用示例 src/api/user.js

javascript
复制
下载
import request from './request'export function login(username, password) {
  return request({
    url: '/user/login',
    method: 'post',
    data: { username, password },
    needEncrypt: true
  })}export function getUserInfo() {
  return request({
    url: '/user/info',
    method: 'get',
    needEncrypt: false
  })}

4. 后端实现(Java)

RSA密钥对生成

java
复制
下载
import javax.crypto.Cipher;import java.security.*;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.Base64;public class RSAUtil {
    
    // 生成RSA密钥对
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.generateKeyPair();
    }
    
    // 获取公钥(Base64编码)
    public static String getPublicKey(KeyPair keyPair) {
        PublicKey publicKey = keyPair.getPublic();
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }
    
    // 获取私钥(Base64编码)
    public static String getPrivateKey(KeyPair keyPair) {
        PrivateKey privateKey = keyPair.getPrivate();
        return Base64.getEncoder().encodeToString(privateKey.getEncoded());
    }
    
    // RSA解密
    public static String decrypt(String encryptedText, String privateKey) throws Exception {
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
        byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey);
        
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey key = keyFactory.generatePrivate(keySpec);
        
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        
        return new String(decryptedBytes);
    }}

AES工具类

java
复制
下载
import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.util.Base64;public class AESUtil {
    
    // AES解密
    public static String decrypt(String encryptedData, String key, String iv) throws Exception {
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] keyBytes = key.getBytes("UTF-8");
        byte[] ivBytes = iv.getBytes("UTF-8");
        
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
        
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        
        return new String(decryptedBytes, "UTF-8");
    }}

Spring Controller示例

java
复制
下载
@RestController@RequestMapping("/api")public class ApiController {
    
    @GetMapping("/publicKey")
    public ResponseEntity<String> getPublicKey() {
        // 从配置或缓存中获取公钥
        String publicKey = "你的RSA公钥";
        return ResponseEntity.ok(publicKey);
    }
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody EncryptedRequest request) {
        try {
            // 1. 解密AES密钥
            String aesKey = RSAUtil.decrypt(request.getKey(), "你的RSA私钥");
            String aesIv = RSAUtil.decrypt(request.getIv(), "你的RSA私钥");
            
            // 2. 解密数据
            String decryptedData = AESUtil.decrypt(request.getData(), aesKey, aesIv);
            
            // 3. 转换为对象
            LoginRequest loginRequest = new ObjectMapper().readValue(decryptedData, LoginRequest.class);
            
            // 4. 处理业务逻辑...
            
            return ResponseEntity.ok("登录成功");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("解密失败");
        }
    }
    
    // 加密请求体
    static class EncryptedRequest {
        private String key;
        private String iv;
        private String data;
        
        // getters and setters
    }
    
    // 实际请求体
    static class LoginRequest {
        private String username;
        private String password;
        
        // getters and setters
    }}

安全建议

  1. 定期更换RSA密钥对:建议每天或每周更换一次

  2. 前端不要存储私钥:私钥永远只存在于后端

  3. 敏感操作二次验证:如支付等操作可以要求用户输入短信验证码

  4. 防重放攻击:可以加入时间戳和随机数,后端校验请求的有效期

  5. 数据签名:对传输的数据进行签名,防止篡改

性能考虑

  1. RSA加密较慢,只用于加密AES密钥

  2. AES加密速度快,用于加密实际数据

  3. 对于不敏感的数据,可以不加密以提升性能

这种方案在安全性和性能之间取得了良好的平衡,适合大多数Web应用场景。


本文标签: 缓存 登录 复制 https http vue

发表评论 (226人参与, 0 条评论)

评论列表

    快来评论,快来抢沙发吧~