rsa非对称加密,加签验签

用户需求

为海南师范高考录取查询接口增加 RSA 非对称验签机制,防止第三方脚本乱刷。调用方为另一个 PHP 服务,使用 RSA 私钥对请求参数签名,Java 后端使用配置的公钥验签,验签不通过则直接拒绝。

产品概述

在现有 HainnuController.query 接口上增加一层服务端到服务端的请求合法性校验。只有持有对应 RSA 私钥的 PHP 调用方才能生成后端可验签通过的请求,从而拦截无密钥的第三方盲刷请求。

核心功能

  • DTO 新增 sign 字段,由调用方随请求体传入 Base64 编码的 RSA 签名值
  • 后端从配置加载 RSA 公钥(X.509 / Base64 格式),使用 SHA256withRSA 算法验签
  • 验签内容固定为 kshsfzh 按约定顺序拼接的字符串(如 ksh=xxx&sfzh=yyy
  • 验签失败直接返回 AjaxResult.error,不执行后续 CSRF 获取、验证码识别与录取查询逻辑
  • 提供 RSA 密钥对生成说明,公钥写入 Java 侧配置,私钥交付 PHP 调用方

技术栈

  • 运行环境:Java 8 + Spring Boot 2.7.18
  • 加密实现:JDK 标准库 java.security(Signature、KeyFactory、X509EncodedKeySpec),无需新增 Maven 依赖
  • 签名算法:SHA256withRSA,密钥长度 RSA 2048,便于与 PHP OpenSSL 互通
  • 公钥格式:X.509 编码的 Base64 字符串,配置于 application.properties,避免硬编码

实现方案

总体策略

HainnuController.query 方法开头插入一段验签逻辑:从 DTO 取出 kshsfzhsign,按固定规则拼接原文,使用注入的公钥做 SHA256withRSA 验签。验签通过才继续后续 OCR 与查询流程;失败立即返回错误。新增一个 RsaVerifyUtil 工具类封装公钥加载与验签,新增一个 RsaPublicKeyConfig 配置类(或直接在现有 config 目录下新增)读取配置公钥并暴露 PublicKey Bean。改动局部、聚焦,不引入拦截器或全局改动。

关键技术决策

  • 仅验签不加密:RSA 验签开销低(一次签名校验约毫秒级),适合作为轻量鉴权;不直接加密业务数据,避免 RSA 长度限制与性能损耗。
  • 复用 JDK 标准库 :项目 pom 无 crypto 依赖,java.security 已满足需求,避免引入 BouncyCastle 等额外依赖,降低复杂度与体积。
  • 拼接原文规则固定 :约定 ksh=值&sfzh=值(URL 风格)作为待签原文,PHP 侧按相同规则构造,避免字段顺序歧义导致验签失败。
  • 公钥配置化 :公钥放 application.properties,便于后续轮换密钥或环境隔离,不写死在代码里。

性能与可靠性

  • 验签在方法最前置执行,失败即返回,避免无谓的 OCR(腾讯云 OCR 有调用成本)与下游 HTTP 请求,直接拦截非法流量。
  • 单次 RSA 验签时间复杂度 O(1)(固定密钥规模),无性能瓶颈;公钥在应用启动时加载为 PublicKey 对象缓存,避免每次请求重复解析。
  • 验签异常(如 sign 为空、Base64 非法、算法异常)统一捕获并返回错误,不让异常穿透到全局异常处理造成敏感信息泄露。

实现注意事项

  • 复用现有 AjaxResult.error 返回结构,保持接口响应格式一致。
  • sign 字段加 @NotBlank 校验,缺失时由 validation 直接拦截。
  • 拼接原文顺序、分隔符需与 PHP 调用方严格一致,建议在代码注释与交付文档中说明。
  • 公钥字符串换行与 PEM 头尾需正确处理(配置中存纯 Base64,去掉 -----BEGIN/END----- 头尾)。
  • 不改动现有 ksh/sfzh 校验逻辑与 OCR/查询流程,仅前置拦截。

架构设计

组件关系

#mermaid-svg-2CCREeaTpncaGjaK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2CCREeaTpncaGjaK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2CCREeaTpncaGjaK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2CCREeaTpncaGjaK .error-icon{fill:#552222;}#mermaid-svg-2CCREeaTpncaGjaK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2CCREeaTpncaGjaK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2CCREeaTpncaGjaK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2CCREeaTpncaGjaK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2CCREeaTpncaGjaK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2CCREeaTpncaGjaK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2CCREeaTpncaGjaK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2CCREeaTpncaGjaK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2CCREeaTpncaGjaK .marker.cross{stroke:#333333;}#mermaid-svg-2CCREeaTpncaGjaK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2CCREeaTpncaGjaK p{margin:0;}#mermaid-svg-2CCREeaTpncaGjaK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2CCREeaTpncaGjaK .cluster-label text{fill:#333;}#mermaid-svg-2CCREeaTpncaGjaK .cluster-label span{color:#333;}#mermaid-svg-2CCREeaTpncaGjaK .cluster-label span p{background-color:transparent;}#mermaid-svg-2CCREeaTpncaGjaK .label text,#mermaid-svg-2CCREeaTpncaGjaK span{fill:#333;color:#333;}#mermaid-svg-2CCREeaTpncaGjaK .node rect,#mermaid-svg-2CCREeaTpncaGjaK .node circle,#mermaid-svg-2CCREeaTpncaGjaK .node ellipse,#mermaid-svg-2CCREeaTpncaGjaK .node polygon,#mermaid-svg-2CCREeaTpncaGjaK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2CCREeaTpncaGjaK .rough-node .label text,#mermaid-svg-2CCREeaTpncaGjaK .node .label text,#mermaid-svg-2CCREeaTpncaGjaK .image-shape .label,#mermaid-svg-2CCREeaTpncaGjaK .icon-shape .label{text-anchor:middle;}#mermaid-svg-2CCREeaTpncaGjaK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2CCREeaTpncaGjaK .rough-node .label,#mermaid-svg-2CCREeaTpncaGjaK .node .label,#mermaid-svg-2CCREeaTpncaGjaK .image-shape .label,#mermaid-svg-2CCREeaTpncaGjaK .icon-shape .label{text-align:center;}#mermaid-svg-2CCREeaTpncaGjaK .node.clickable{cursor:pointer;}#mermaid-svg-2CCREeaTpncaGjaK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2CCREeaTpncaGjaK .arrowheadPath{fill:#333333;}#mermaid-svg-2CCREeaTpncaGjaK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2CCREeaTpncaGjaK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2CCREeaTpncaGjaK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2CCREeaTpncaGjaK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2CCREeaTpncaGjaK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2CCREeaTpncaGjaK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2CCREeaTpncaGjaK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2CCREeaTpncaGjaK .cluster text{fill:#333;}#mermaid-svg-2CCREeaTpncaGjaK .cluster span{color:#333;}#mermaid-svg-2CCREeaTpncaGjaK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2CCREeaTpncaGjaK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2CCREeaTpncaGjaK rect.text{fill:none;stroke-width:0;}#mermaid-svg-2CCREeaTpncaGjaK .icon-shape,#mermaid-svg-2CCREeaTpncaGjaK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2CCREeaTpncaGjaK .icon-shape p,#mermaid-svg-2CCREeaTpncaGjaK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2CCREeaTpncaGjaK .icon-shape .label rect,#mermaid-svg-2CCREeaTpncaGjaK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2CCREeaTpncaGjaK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2CCREeaTpncaGjaK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2CCREeaTpncaGjaK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ksh,sfzh,sign
验签通过
验签失败
PHP 调用方
HainnuController.query
RsaVerifyUtil.verify
RsaPublicKeyConfig PublicKey Bean
原有 OCR + 查询流程
AjaxResult.error 直接返回

架构无新增分层,仅新增工具类与配置类,控制器内部调用,符合现有轻量结构。

目录结构

复制代码
src/main/java/com/yunlei/
├── config/
│   └── RsaPublicKeyConfig.java          # [NEW] 读取 application.properties 中的 RSA 公钥 Base64,启动时解析为 PublicKey Bean 并缓存。提供 getPublicKey() 供验签使用。
├── utils/
│   └── RsaVerifyUtil.java               # [NEW] RSA 验签工具。封装 SHA256withRSA 验签方法 verify(plainText, base64Sign, publicKey),处理 Base64 解码与异常归一化。
├── domain/dto/
│   └── HainnuGaoKaoAdmissionQueryDto.java   # [MODIFY] 新增 @NotBlank String sign 字段,作为调用方传入的 Base64 签名值。
├── controller/
│   └── HainnuController.java            # [MODIFY] 在 query 方法开头注入 RsaVerifyUtil 与 PublicKey,按约定拼接 ksh/sfzh 并验签;失败返回 AjaxResult.error,通过才继续原有逻辑。
└── resources/
    └── application.properties           # [MODIFY] 新增 rsa.public-key= 配置项,存放 X.509 Base64 公钥(去头尾)。

关键代码结构

java 复制代码
// RsaVerifyUtil.java 核心接口签名
public class RsaVerifyUtil {
    public static boolean verify(String plainText, String base64Signature, PublicKey publicKey) throws SignatureException;
}

// RsaPublicKeyConfig.java 核心接口签名
@Configuration
public class RsaPublicKeyConfig {
    @Bean
    public PublicKey rsaPublicKey(@Value("${rsa.public-key}") String base64Key) throws GeneralSecurityException;
}
相关推荐
Sagittarius_A*1 天前
公钥密码基础(三):RSA 数学原理:欧拉定理、模逆与快速幂
信息安全·密码学·rsa·公钥密码·密钥分发
RuoyiOffice3 天前
抓包还能看到密码?Vue3 + Spring Boot 3 接口 AES/RSA 加解密(@ApiEncrypt)全链路实战
spring boot·vue3·aes·rsa·接口安全·spring boot 3·apiencrypt
Mysticbinary4 天前
非对称加密(RSA算法)原理
非对称加密·rsa
Looooking10 天前
Python 之 RSA 加解密和密码校验
密码·rsa
要开心吖ZSH12 天前
公钥加密,私钥解密?私钥加密,公钥解密?哪个对?
密码学·aes·sha·rsa·sm2·sm3·签名验签
Ab_stupid2 个月前
CTF-Crypto培训笔记-现代密码
笔记·des·aes·rsa·crypto
张一西3 个月前
嵌入式系统中的加解密签名(3)---国密的签名与验证
签名·rsa·验签·sm2
恼书:-(空寄3 个月前
深入理解敏感信息加密:AES+RSA混合使用前后端对接
aes·数据加密·rsa
Asurplus3 个月前
【VUE】17、使用JSEncrypt对数据加解密
javascript·vue.js·jsencrypt·rsa