Vue 前端 RSA 密码加密登录:从原理到落地

标签:Vue · 前端安全 · RSA · JSEncrypt · 登录鉴权
更新时间:2026-06-29


前言

做 Web 登录时,一个常见问题是:密码能不能明文传给后端?

答案当然是不行。即使用了 HTTPS,在浏览器到服务端这段链路里,仍有不少场景可能暴露敏感信息——抓包调试、日志打印、中间代理、XSS 脚本读取表单等。因此,很多团队会在前端对密码做 RSA 非对称加密:浏览器只持有公钥,服务端持有私钥,密码以密文形式传输,降低泄露风险。

本文不绑定任何具体业务项目,只讲一套 Vue + JSEncrypt + RSA 的通用登录加密方案,可直接用于博客分享或新项目落地。


一、为什么选 RSA,而不是自己「发明」加密?

先明确一件事:RSA 不是用来替代 HTTPS 的,而是 HTTPS 之上的一层业务防护。

方案 优点 缺点
明文传输 实现简单 风险最高
前端 MD5/SHA 看起来「加密了」 可被重放,且哈希值本身可当密码用
AES 对称加密 速度快 密钥放前端等于公开
RSA 非对称加密 公钥可公开,私钥只在服务端 密文较长,不适合大数据

典型分工:

  • 前端:拿公钥,把用户输入的密码加密成 Base64 密文
  • 后端:拿私钥解密,得到明文后再做 BCrypt / Argon2 等哈希比对

这就是「锁和钥匙分离」:公钥相当于锁,谁都能锁;私钥相当于钥匙,只有服务端能开。


二、整体流程(一张图看懂)

后端服务 请求层 前端页面 用户 后端服务 请求层 前端页面 用户 输入账号 + 密码 表单校验 RSA 公钥加密密码 提交登录参数 POST /api/auth/login RSA 私钥解密 校验密码 + 签发 Token 返回 Token 与用户信息 持久化登录态 登录成功 跳转首页

一句话总结:用户看到的是明文密码,网络上传的是密文,数据库里存的是哈希。


三、技术选型

层级 推荐方案
前端框架 Vue 2 / Vue 3 均可
HTTP Axios / Fetch
加密库 JSEncrypt
算法 RSA-2048,PKCS#1 v1.5 填充
公钥存放 环境变量或独立配置文件
私钥存放 仅服务端,不进仓库、不进前端

安装依赖:

npm install jsencrypt

四、前端实现(通用写法)

4.1 配置公钥

公钥建议放在环境变量中,便于不同环境切换:

# .env.development / .env.production
VUE_APP_RSA_PUBLIC_KEY=你的RSA公钥Base64字符串

公钥可以公开;私钥绝不能出现在前端代码或环境变量里。

4.2 封装加密工具函数

import JSEncrypt from 'jsencrypt'

/**
 * 使用 RSA 公钥加密明文
 * @param {string} plainText - 待加密字符串(如密码)
 * @returns {string|null} Base64 密文,失败返回 null
 */
export function rsaEncrypt(plainText) {
  const publicKey = process.env.VUE_APP_RSA_PUBLIC_KEY
  if (!publicKey || !plainText) return null

  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey)
  return encryptor.encrypt(plainText)
}

4.3 登录页调用示例

以下是完全独立的示例代码,展示「校验 → 加密 → 请求 → 存 Token」的标准写法:

import { rsaEncrypt } from '@/utils/rsaEncrypt'
import { loginApi } from '@/api/auth'

export default {
  data() {
    return {
      form: { username: '', password: '' },
      loading: false
    }
  },
  methods: {
    async handleLogin() {
      if (!this.form.username || !this.form.password) {
        this.$message.warning('请输入账号和密码')
        return
      }

      const encryptedPwd = rsaEncrypt(this.form.password)
      if (!encryptedPwd) {
        this.$message.error('密码加密失败,请稍后重试')
        return
      }

      this.loading = true
      try {
        const res = await loginApi({
          username: this.form.username,
          password: encryptedPwd
        })
        // 保存 Token,跳转首页
        localStorage.setItem('token', res.data.token)
        this.$router.push('/')
      } finally {
        this.loading = false
      }
    }
  }
}

4.4 请求体长什么样?

假设用户输入密码 123456,前端发出的 JSON 大致如下:

{
  "username": "demo_user",
  "password": "pY8Ve/JioBQFDjF++NzpHHREHoWp9s1CdZ/MixYlBb+..."
}

其中 password 字段是 RSA 加密后的 Base64 字符串,不是明文。

4.5 密文示例说明(明文:123456)

项目 说明
明文 123456
算法 RSA-2048 / PKCS#1 v1.5
输出 Base64 字符串

一个重要细节:RSA 使用 PKCS#1 填充时,同一明文每次加密结果可能不同(填充含随机数),但后端解密后都能得到相同的 123456。所以 Postman 调试时,不要复制旧密文长期使用,应每次重新加密。

本地验证加密是否正常,可用 Node.js 内置 crypto 模块:

const crypto = require('crypto')

// 将 Base64 公钥转为 PEM 格式
function toPem(base64Key) {
  const lines = base64Key.match(/.{1,64}/g).join('\n')
  return `-----BEGIN PUBLIC KEY-----\n${lines}\n-----END PUBLIC KEY-----`
}

const publicKeyBase64 = '你的公钥Base64字符串'
const pem = toPem(publicKeyBase64)

const cipher = crypto.publicEncrypt(
  { key: pem, padding: crypto.constants.RSA_PKCS1_PADDING },
  Buffer.from('123456', 'utf8')
)

console.log(cipher.toString('base64'))

五、后端如何解密?

后端收到密文后,用 RSA 私钥 解密,得到明文密码,再进入常规鉴权流程。

5.1 Java(Spring Boot)

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class RsaDecryptUtil {

    // 私钥仅保存在服务端配置中心或密钥管理服务中
    private static final String PRIVATE_KEY_BASE64 = "你的私钥Base64字符串";

    public static String decrypt(String cipherTextBase64) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(PRIVATE_KEY_BASE64);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);

        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        byte[] plainBytes = cipher.doFinal(Base64.getDecoder().decode(cipherTextBase64));
        return new String(plainBytes, StandardCharsets.UTF_8);
    }
}

Controller 中的典型用法:

@PostMapping("/api/auth/login")
public ApiResult login(@RequestBody LoginRequest req) throws Exception {
    String plainPassword = RsaDecryptUtil.decrypt(req.getPassword());
    // plainPassword 此时为 "123456",再与数据库哈希比对
    return authService.login(req.getUsername(), plainPassword);
}

5.2 C#(.NET 6+)

using System.Security.Cryptography;
using System.Text;

public static class RsaHelper
{
    private const string PrivateKeyBase64 = "你的私钥Base64字符串";

    public static string Decrypt(string cipherTextBase64)
    {
        var rsa = RSA.Create();
        rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(PrivateKeyBase64), out _);

        var cipherBytes = Convert.FromBase64String(cipherTextBase64);
        var plainBytes = rsa.Decrypt(cipherBytes, RSAEncryptionPadding.Pkcs1);

        return Encoding.UTF8.GetString(plainBytes); // 例如 "123456"
    }
}

5.3 后端加密(接口测试 / 第三方对接)

若需要在 Postman 或脚本里模拟前端加密:

string RsaEncrypt(string plainText, string publicKeyBase64)
{
    var rsa = RSA.Create();
    rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKeyBase64), out _);
    var bytes = rsa.Encrypt(Encoding.UTF8.GetBytes(plainText), RSAEncryptionPadding.Pkcs1);
    return Convert.ToBase64String(bytes);
}

var encrypted = RsaEncrypt("123456", publicKey);

六、密钥怎么生成?

开发阶段可用 OpenSSL 快速生成一对 2048 位密钥:

# 生成私钥
openssl genrsa -out private.pem 2048

# 从私钥导出公钥
openssl rsa -in private.pem -pubout -out public.pem

# 转为 PKCS#8 格式(Java 常用)
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private.pem -out private_pkcs8.pem
  • 公钥 → 配置到前端环境变量
  • 私钥 → 配置到后端,权限最小化,禁止提交 Git

七、还有哪些场景可以用同一套方案?

只要涉及「敏感字符串从前端传到后端」,都可以复用同一公钥加密逻辑:

场景 加密字段
用户登录 password
修改密码 oldPassword、newPassword、confirmPassword
重置密码 newPassword
第三方凭证绑定 secret、token 等短字符串

原则:短文本、低频传输 适合 RSA;大段数据应改用 AES + RSA 混合加密或纯 HTTPS 传输。


八、踩坑清单(血泪经验)

1. 前后端填充方式不一致

JSEncrypt 默认 PKCS#1 v1.5,Java 需用 RSA/ECB/PKCS1Padding,.NET 需用 RSAEncryptionPadding.Pkcs1。填错一个字母,解密就是乱码。

2. 公钥私钥不是一对

前端换了公钥,后端没换私钥,或者开发/生产环境密钥混用,登录永远失败。建议密钥按环境隔离管理。

3. 以为密文可以复用

RSA 密文带随机填充,不能把 A 时刻的密文当作 B 时刻的密码用。自动化测试要每次动态加密。

4. 把 RSA 当万能盾

  • RSA 防不了 XSS(脚本仍能读输入框明文)
  • RSA 防不了重放(需配合 nonce、时间戳、HTTPS)
  • RSA 替代不了密码哈希存储

正确姿势:传输加密(RSA)+ 存储哈希(BCrypt)+ 传输通道(HTTPS),三层各司其职。

5. 私钥泄露

私钥一旦进 Git 历史,等于所有历史密文都可被离线解密。务必用 .gitignore、密钥管理服务、CI 注入等方式隔离。


九、落地 Checklist

对接时可按此清单逐项核对:

  • 生成 RSA-2048 密钥对,公钥给前端,私钥给后端
  • 前端安装 jsencrypt,封装 rsaEncrypt 工具函数
  • 登录接口传参前对密码字段加密
  • 后端登录接口先解密,再校验哈希
  • 前后端统一 PKCS#1 v1.5 填充
  • 生产环境启用 HTTPS
  • 私钥不进仓库、不进前端
  • Postman / 自动化测试支持动态加密

十、总结

Vue 登录密码 RSA 加密并不复杂,核心就四步:

  1. 前端用公钥把密码锁起来
  2. 网络上传输的是 Base64 密文
  3. 后端用私钥打开,得到明文
  4. 数据库只存哈希,不存明文

123456 为例:用户输入的是 123456,线上飞的是一长串 Base64,后端解出来还是 123456,最后和库里 BCrypt 哈希比对——各司其职,清晰可控。

如果你正在设计登录模块,希望这篇能帮你少踩几个坑。欢迎收藏、转发,也欢迎在评论区交流你的实践经验。


延伸阅读

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐