Springboot實現一個接口加密

首先來看效果

image-20250713141307986

這個主要是為了防止篡改請求的。

我們這里采用的是一個AOP的攔截,在有需要這樣的接口上添加了加密處理。

下面是一些功能

防篡改HMAC-SHA256 參數簽名密鑰僅客戶端 & 服務器持有
防重放秒級時間戳 + 有效窗口校驗默認允許 ±5 分鐘
防竊聽AES/CBC/PKCS5Padding 加密業務體對稱密鑰 16/24/32 字符
最小侵入Spring AOP + 自定義注解@SecureApi 一行即可啟用

前后端交互流程

  1. 前端:在請求攔截器里自動
    • 生成 timestamp
    • 將業務 JSON → AES 加密得到 data
    • 按字典序拼接 timestamp=data,用 HMAC-SHA256 生成 sign
  2. 后端切面:僅攔截被 @SecureApi 標記的方法/類
    • 解析三字段 → 校驗時間窗口
    • 移除 sign 再驗簽
    • 成功后解密 data → 注入 request.setAttribute("secureData", plaintext)

源碼部分

首先是定義一個注解。

/*** 在 Controller 方法或類上添加該注解后,將啟用參數簽名、時間戳校驗和 AES 解密校驗。*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SecureApi {
}

最主要的攔截器

package com.xiaou.secure.aspect;import com.xiaou.secure.exception.SecureException;
import com.xiaou.secure.properties.SecureProperties;
import com.xiaou.secure.util.AESUtil;
import com.xiaou.secure.util.SignUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.io.BufferedReader;
import java.io.IOException;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;/*** 安全校驗切面*/
@Aspect
@Component
public class SecureAspect {private static final Logger log = LoggerFactory.getLogger(SecureAspect.class);@Autowiredprivate SecureProperties properties;@Around("@annotation(com.xiaou.secure.annotation.SecureApi)")public Object around(ProceedingJoinPoint pjp) throws Throwable {ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attrs == null) {return pjp.proceed();}HttpServletRequest request = attrs.getRequest();Map<String, String> params = extractParams(request);// 1. 時間戳校驗validateTimestamp(params.get("timestamp"));// 2. 簽名校驗validateSign(params);// 3. AES 解密 data 字段if (params.containsKey("data")) {String plaintext = AESUtil.decrypt(params.get("data"), properties.getAesKey());// 把解密后的內容放到 request attribute,方便業務層讀取request.setAttribute("secureData", plaintext);}return pjp.proceed();}private Map<String, String> extractParams(HttpServletRequest request) throws IOException {Map<String, String[]> parameterMap = request.getParameterMap();Map<String, String> params = new HashMap<>();parameterMap.forEach((k, v) -> params.put(k, v[0]));// 如果沒有參數,但可能是 JSON body,需要讀取 bodyif (params.isEmpty() && request.getContentType() != null&& request.getContentType().startsWith("application/json")) {String body = readBody(request);if (body != null && !body.isEmpty()) {try {com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();Map<String, Object> jsonMap = mapper.readValue(body, Map.class);jsonMap.forEach((k, v) -> params.put(k, v == null ? null : v.toString()));} catch (Exception e) {// 回退到原始 & 分隔的解析方式,兼容 x-www-form-urlencoded 字符串Arrays.stream(body.split("&")).forEach(kv -> {String[] kvArr = kv.split("=", 2);if (kvArr.length == 2) {params.put(kvArr[0], kvArr[1]);}});}}}return params;}private String readBody(HttpServletRequest request) throws IOException {StringBuilder sb = new StringBuilder();try (BufferedReader reader = request.getReader()) {String line;while ((line = reader.readLine()) != null) {sb.append(line);}}return sb.toString();}private void validateTimestamp(String timestampStr) {if (timestampStr == null) {throw new SecureException("timestamp missing");}long ts;try {ts = Long.parseLong(timestampStr);} catch (NumberFormatException e) {throw new SecureException("timestamp invalid");}long now = Instant.now().getEpochSecond();if (Math.abs(now - ts) > properties.getAllowedTimestampOffset()) {throw new SecureException("timestamp expired");}}private void validateSign(Map<String, String> params) {String sign = params.remove("sign");if (sign == null) {throw new SecureException("sign missing");}// 排序Map<String, String> sorted = params.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, LinkedHashMap::new));String expected = SignUtil.sign(sorted, properties.getSignSecret());if (!Objects.equals(expected, sign)) {throw new SecureException("sign invalid");}}
}

配置方面:

springboot自動配置

@Configuration
@ConditionalOnClass(WebMvcConfigurer.class)
@AutoConfigureAfter(name = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration")
public class SecureAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic SecureProperties secureProperties() {return new SecureProperties();}
}

動態配置 當然也可以用靜態的

/*** 安全模塊配置*/
@ConfigurationProperties(prefix = "secure")
public class SecureProperties {/*** AES 密鑰(16/24/32 位)*/// 默認 16 字符,避免 InvalidKeyExceptionprivate String aesKey = "xiaou-secure-123";/*** 簽名密鑰*/private String signSecret = "xiaou-sign-secret";/*** 允許的時間差 (秒),默認 300 秒*/private long allowedTimestampOffset = 300;public String getAesKey() {return aesKey;}public void setAesKey(String aesKey) {this.aesKey = aesKey;}public String getSignSecret() {return signSecret;}public void setSignSecret(String signSecret) {this.signSecret = signSecret;}public long getAllowedTimestampOffset() {return allowedTimestampOffset;}public void setAllowedTimestampOffset(long allowedTimestampOffset) {this.allowedTimestampOffset = allowedTimestampOffset;}
}

工具類:

package com.xiaou.secure.util;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;/*** AES/CBC/PKCS5Padding 工具類*/
public class AESUtil {private static final String AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";private static final String AES = "AES";private AESUtil() {}public static String encrypt(String data, String key) {try {Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {throw new RuntimeException("AES encrypt error", e);}}public static String decrypt(String cipherText, String key) {try {Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5);SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);IvParameterSpec iv = new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);byte[] original = cipher.doFinal(Base64.getDecoder().decode(cipherText));return new String(original, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("AES decrypt error", e);}}
}
package com.xiaou.secure.util;import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.StringJoiner;/*** 簽名工具類*/
public class SignUtil {private SignUtil() {}/*** 生成簽名* * @param params 不包含 sign 的參數 map,已按字典序排序* @param secret 秘鑰*/public static String sign(Map<String, String> params, String secret) {StringJoiner sj = new StringJoiner("&");params.forEach((k, v) -> sj.add(k + "=" + v));String data = sj.toString();return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secret.getBytes(StandardCharsets.UTF_8)).hmacHex(data);}
}

以上就是全部源碼

如果想要看具體的一個實現可以參考我的開源項目里面的xiaou-common-secure模塊 https://github.com/xiaou61/U-space

使用流程

在需要的接口上添加注解

	@SecureApi                // 生效!@PostMapping("/student/save")public R<Void> saveStudent(HttpServletRequest request) {String json = (String) request.getAttribute("secureData"); // 解密后明文StudentDTO dto = JSON.parseObject(json, StudentDTO.class);//其他業務操作return R.ok();}
}

前端接入

1. 安裝依賴

npm i crypto-js

2. 編寫工具 (src/utils/secure.js)

import CryptoJS from 'crypto-js';const AES_KEY  = import.meta.env.VITE_AES_KEY;      // 16/24/32 字符,與后端保持一致
const SIGN_KEY = import.meta.env.VITE_SIGN_SECRET;  // 與后端 sign-secret 一致// AES/CBC/PKCS5Padding 加密 → Base64
export function aesEncrypt(plainText) {const key = CryptoJS.enc.Utf8.parse(AES_KEY);const iv  = CryptoJS.enc.Utf8.parse(AES_KEY.slice(0, 16));const encrypted = CryptoJS.AES.encrypt(plainText, key, {iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7});return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}// 生成簽名:字典序拼接后做 HMAC-SHA256
export function sign(params) {const sortedStr = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&');return CryptoJS.HmacSHA256(sortedStr, SIGN_KEY).toString();
}

封裝

import http from './request'
import { aesEncrypt, sign as genSign } from './secure'// securePost 重新實現:封裝 { timestamp, data: cipher, sign }export async function securePost (url, bizData = {}, { encrypt = true } = {}) {const timestamp = Math.floor(Date.now() / 1000) // 秒級時間戳,和后端配置一致// 若開啟加密,將 bizData 加密為 Base64 字符串const cipherText = encrypt ? aesEncrypt(bizData) : JSON.stringify(bizData)// 組裝待簽名參數const payload = {timestamp,data: cipherText}// 生成簽名payload.sign = genSign(payload)// 發送 JSONreturn http.post(url, payload, {headers: {'Content-Type': 'application/json'}})
}// 向后兼容:導出舊別名
export { securePost as securePostV2 } 

調用

export const login = (data) => {// 學生登錄接口使用新的 securePost (AES/CBC + HMAC-SHA256)return securePost('/student/auth/login', data)
}

原理解析

這個接口加密機制的出發點其實很簡單:

我們不希望別人偽造請求或者直接看到請求內容。尤其是在登錄、提交表單這種接口上,如果不做處理,參數一旦被篡改或者被抓包,后果可能挺嚴重。

所以我們在請求中加了一些“安全三件套”:

第一是簽名。前端每次發請求的時候,會把參數(主要是 timestamp 和加密后的 data)按字典序拼起來,然后用我們雙方約定好的一個密鑰生成一個簽名(HMAC-SHA256 算法)。后端拿到請求后,同樣的算法再生成一遍簽名,兩個對不上就直接拒絕。這個方式能有效防止參數被篡改。

第二是時間戳。我們不允許別人把一兩分鐘前抓到的請求再發一次,所以前端在請求里帶上當前時間(秒級)。后端檢查這個時間是否還在允許的時間窗口(比如前后 5 分鐘)內,超了就拒絕。這個能防止重放攻擊。

第三是加密。我們不希望別人看到業務參數,比如手機號、密碼、驗證碼這類字段,所以前端用 AES(CBC 模式)把整個業務數據 JSON 加密成密文,后端收到后再解密拿出真實參數。密鑰是我們自己設定的,別人拿不到。

整套邏輯通過 Spring AOP 實現,不需要每個接口去寫重復代碼,只要在 Controller 上加一個 @SecureApi 注解就行了。請求數據校驗通過后,解密出來的原始 JSON 會通過 request.setAttribute("secureData", plaintext) 注入進去,業務代碼直接拿就行。

整體上,這個方案是為了在不增加太多開發成本的前提下,做到參數不可篡改、請求不可復用、敏感數據不可明文傳輸。

流程圖

image-20250713144310930

image-20250713144325491

高清流程圖

https://yxy7auidhk0.feishu.cn/wiki/LuXjwlXjxiFk4tkgrUEc0Ppbn4n?from=from_copylink

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914343.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914343.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914343.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

斯坦福 CS336 動手大語言模型 Assignment1 BPE Tokenizer TransformerLM

所有代碼更新至 https://github.com/WangYuHang-cmd/CS336/tree/main/assignment1-basics 作業文件結構: CS336/assignment1-basics/ ├── tests/ # 測試文件目錄 │ ├── adapters.py # 適配器測試 │ ├── conftest.py # pyt…

Spring Cloud Gateway 實戰指南

關鍵詞&#xff1a;微服務、API網關、Spring Cloud Gateway、路由轉發、限流熔斷 ? 文章摘要 隨著互聯網應用規模的不斷擴大&#xff0c;傳統的單體架構逐漸向微服務架構轉型。在微服務架構中&#xff0c;API 網關作為系統的入口點&#xff0c;承擔了諸如請求路由、負載均衡、…

PyTorch自動微分:從基礎到實戰

目錄 1. 自動微分是什么&#xff1f; 1.1 計算圖 1.2 requires_grad 屬性 2. 標量和向量的梯度計算 2.1 標量梯度 2.2 向量梯度 3. 梯度上下文控制 3.1 禁用梯度計算 3.2 累計梯度 4. 梯度下降實戰 4.1 求函數最小值 4.2 線性回歸參數求解 5. 總結 在深度學習中&a…

Spring AI 項目實戰(十六):Spring Boot + AI + 通義萬相圖像生成工具全棧項目實戰(附完整源碼)

系列文章 序號文章名稱1Spring AI 項目實戰(一):Spring AI 核心模塊入門2Spring AI 項目實戰(二):Spring Boot + AI + DeepSeek 深度實戰(附完整源碼)3Spring AI 項目實戰(三):Spring Boot + AI + DeepSeek 打造智能客服系統(附完整源碼)4

從零到一:企業如何組建安全團隊

在這個"黑客滿天飛&#xff0c;漏洞遍地跑"的時代&#xff0c;沒有安全團隊的企業就像裸奔的勇士——雖然很有勇氣&#xff0c;但結局往往很悲慘。 &#x1f4cb; 目錄 為什么要組建安全團隊安全團隊的核心職能團隊架構設計人員配置策略技術體系建設制度流程建立實施…

業務訪問控制-ACL與包過濾

業務訪問控制-ACL與包過濾 ACL的定義及應用場景ACL&#xff08;Access Control List&#xff0c;訪問控制列表&#xff09;是用來實現數據包識別功能的&#xff1b;ACL可以應用于諸多場景&#xff1a; 包過濾功能&#xff1a;對數據包進行放通或過濾操作。NAT&#xff08;Netwo…

穿梭時空的智慧向導:Deepoc具身智能如何賦予導覽機器人“人情味”

穿梭時空的智慧向導&#xff1a;Deepoc具身智能如何賦予導覽機器人“人情味”清晨&#xff0c;當第一縷陽光透過高大的彩繪玻璃窗&#xff0c;灑在博物館光潔的地板上&#xff0c;一位特別的“館員”已悄然“蘇醒”。它沒有制服&#xff0c;卻有著清晰的指引&#xff1b;它無需…

PostgreSQL 查詢庫中所有表占用磁盤大小、表大小

SELECTn.nspname AS schema_name,c.relname AS table_name,-- 1?? 總大小&#xff08;表 toast 索引&#xff09;pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,-- 2?? 表不包含索引&#xff08;含 TOAST&#xff09;pg_size_pretty(pg_total_relation_s…

日記-生活隨想

最近鼠鼠也是來到上海打拼&#xff08;實習&#xff09;了&#xff0c;那么秉持著來都來了的原則&#xff0c;鼠鼠也是去bw逛了逛&#xff0c;雖說沒票只能在外場看看&#x1f62d;。可惜幾乎沒有多少我非常喜歡的ip&#xff0c;不由感慨現在的二次元圈已經變樣了。雖說我知道內…

串口A和S的含義以及RT的含義

A async 異步S sync 同步RT 收發U A RT 異步U SA RT 同步/異步

spring cloud負載均衡分析之FeignBlockingLoadBalancerClient、BlockingLoadBalancerClient

本文主要分析被 FeignClient 注解的接口類請求過程中負載均衡邏輯&#xff0c;流程分析使用的依賴版本信息如下&#xff1a;<spring-boot.version>3.2.1</spring-boot.version><spring-cloud.version>2023.0.0</spring-cloud.version><com.alibaba.…

ref 和 reactive

文章目錄ref 和 reactive一、差異二、能否替代的場景分析&#xff08;1&#xff09;基本類型數據&#xff08;2&#xff09;對象類型數據&#xff08;3&#xff09;數組類型數據&#xff08;4&#xff09; 需要整體替換的場景三、替代方案與兼容寫法1. 用 reactive 模擬 ref2. …

BatchNorm 與 LayerNorm:原理、實現與應用對比

BatchNorm 與 LayerNorm&#xff1a;原理、實現與應用對比 Batch Normalization (批歸一化) 和 Layer Normalization (層歸一化) 是深度學習中兩種核心的歸一化技術&#xff0c;它們解決了神經網絡訓練中的內部協變量偏移問題&#xff0c;大幅提升了模型訓練的穩定性和收斂速度…

OcsNG基于debian一鍵部署腳本

&#x1f914; 為什么有了GLPI還要部署OCS-NG&#xff1f; 核心問題&#xff1a;數據收集的風險 GLPI直接收集的問題&#xff1a; Agent直接向GLPI報告數據時&#xff0c;任何收集異常都會直接影響資產數據庫網絡問題、Agent故障可能導致重復資產、錯誤數據、資產丟失無法對收集…

001_Claude開發者指南介紹

Claude開發者指南介紹 目錄 Claude簡介Claude 4 模型開始使用核心功能支持資源 Claude簡介 Claude 是由 Anthropic 構建的高性能、可信賴和智能的 AI 平臺。Claude 具備出色的語言、推理、分析和編程能力&#xff0c;可以幫助您解決各種復雜任務。 想要與 Claude 聊天嗎&a…

004_Claude功能特性與API使用

Claude功能特性與API使用 目錄 API 基礎使用核心功能特性高級功能開發工具平臺支持 API 基礎使用 快速開始 通過 Anthropic Console 獲取 API 訪問權限&#xff1a; 在 console.anthropic.com/account/keys 生成 API 密鑰使用 Workbench 在瀏覽器中測試 API 認證方式 H…

ReAct論文解讀(1)—什么是ReAct?

什么是ReAct&#xff1f; 在大語言模型&#xff08;LLM&#xff09;領域中&#xff0c;ReAct 指的是一種結合了推理&#xff08;Reasoning&#xff09; 和行動&#xff08;Acting&#xff09; 的提示方法&#xff0c;全稱是 “ReAct: Synergizing Reasoning and Acting in Lan…

【云服務器安全相關】服務器防火墻常見系統日志信息說明

目錄? 一、防火墻日志是做什么的&#xff1f;&#x1f6e0;? 二、常見防火墻日志信息及說明&#x1f9ea; 三、典型日志示例解析1. 被阻斷的訪問&#xff08;DROP&#xff09;2. 被允許的訪問&#xff08;ACCEPT&#xff09;3. 被拒絕的端口訪問4. 可疑端口掃描行為&#x1f…

011_視覺能力與圖像處理

視覺能力與圖像處理 目錄 視覺能力概述支持的圖像格式圖像上傳方式使用限制最佳實踐應用場景API使用示例視覺能力概述 多模態交互 Claude 3 系列模型具備強大的視覺理解能力,可以分析和理解圖像內容,實現真正的多模態AI交互。這種能力使Claude能夠: 圖像內容分析:理解圖…

ansible自動化部署考試系統前后端分離項目

1. ?ansible編寫劇本步驟1??創建roles目錄結構2??在group_vars/all/main.yml中定義變量列表3??在tasks目錄下編寫tasks任務4??在files目錄下準備部署文件5??在templates目錄下創建j2模板文件6??在handlers目錄下編寫handlers7??在roles目錄下編寫主playbook8??…