news 2026/6/10 15:33:27

API 接口裸奔?Spring Security OAuth2 + JWT + 签名验签,打造金融级的接口安全护盾

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
API 接口裸奔?Spring Security OAuth2 + JWT + 签名验签,打造金融级的接口安全护盾

😱 前言:HTTPS 就够了吗?

很多同学觉得:“我上了 HTTPS,数据就是加密传输的,很安全。”
错!HTTPS 只能防止数据在传输链路上不被窃听,但它防不住:

  1. 中间人攻击(MITM):如果客户端被植入根证书,HTTPS 一样被抓包。
  2. 重放攻击:黑客截获你的请求,虽然解不开,但他可以把这个请求重复发送 100 次(比如转账接口)。
  3. 参数篡改:黑客修改了转账金额,而服务端只认 Token,不校验金额是否被改过。

要达到金融级安全,我们需要“三道防线”


🏗️ 一、 架构设计:三道防线

我们将安全逻辑封装在一个全局 Filter 中,请求到达 Controller 之前必须通关。

安全防御流程图 (Mermaid):

三道防线

1. 携带 Token + Sign + Timestamp
2. 防重放校验

通过

通过

全部通过

过期/重复

签名错误

无效 Token

客户端请求

API 网关 / 过滤器

校验 Timestamp & Nonce

3. 参数签名验签 (Sign)
4. OAuth2 JWT 认证

业务逻辑 Controller

403 Forbidden

401 Unauthorized

401 Unauthorized


🔐 二、 第一道防线:OAuth2 + JWT (身份认证)

这是最基础的“门票”。我们使用 Spring Security OAuth2 Resource Server 来解析 JWT。

依赖引入:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>

配置 SecurityFilterChain:

@Configuration@EnableWebSecuritypublicclassSecurityConfig{@BeanpublicSecurityFilterChainfilterChain(HttpSecurityhttp)throwsException{http.csrf(csrf->csrf.disable()).sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(auth->auth.requestMatchers("/api/public/**").permitAll().anyRequest().authenticated())// 启用 JWT 解析.oauth2ResourceServer(oauth2->oauth2.jwt(Customizer.withDefaults()));returnhttp.build();}}

这步之后,接口有了基本的“登录”功能,但还不够。


✍️ 三、 第二道防线:参数签名 (防篡改)

这是金融级安全的核心。
原理:客户端把所有参数按 ASCII 码排序,拼接成字符串,加上一个双方约定的SecretKey,进行 MD5 或 SHA256 运算,生成sign
服务端收到请求后,用同样的逻辑算一遍。如果算出来的sign和传过来的不一样,说明参数被篡改了。

签名工具类 (SignatureUtils):

importorg.apache.commons.codec.digest.DigestUtils;importjava.util.TreeMap;importjava.util.Map;publicclassSignatureUtils{// 双方约定的盐值,绝对不能暴露privatestaticfinalStringSALT="Abc@123456_Financial_Secret";/** * 生成签名 * @param params 所有的业务参数 + timestamp + nonce */publicstaticStringgenerateSign(Map<String,String>params){// 1. 使用 TreeMap 进行 ASCII 排序TreeMap<String,String>sorted=newTreeMap<>(params);StringBuildersb=newStringBuilder();for(Map.Entry<String,String>entry:sorted.entrySet()){// 排除 sign 本身和空值if(!"sign".equals(entry.getKey())&&entry.getValue()!=null){sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");}}// 2. 拼接盐值sb.append("key=").append(SALT);// 3. SHA256 摘要returnDigestUtils.sha256Hex(sb.toString()).toUpperCase();}}

⏱️ 四、 第三道防线:Timestamp + Nonce (防重放)

即使黑客拿到了合法的sign,他如果把这串请求拦截下来,隔了 10 分钟再发一遍怎么办?
我们需要“时效性”和“唯一性”校验。

  1. Timestamp:请求带上时间戳。服务端判断:当前时间 - 请求时间 > 60秒,直接拒绝。
  2. Nonce:随机流水号。服务端将处理过的 Nonce 存入 Redis(有效期 60 秒)。如果发现 Redis 里已经有这个 Nonce,说明是重复请求,拒绝。

🛡️ 五、 终极合体:自定义安全过滤器

我们将上述逻辑封装在一个 Filter 中,放在 Spring Security 过滤器链的上游。

@ComponentpublicclassApiSecurityFilterextendsOncePerRequestFilter{@AutowiredprivateStringRedisTemplateredisTemplate;@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{// 1. 获取 Header 中的安全参数Stringsign=request.getHeader("X-Sign");Stringtimestamp=request.getHeader("X-Timestamp");Stringnonce=request.getHeader("X-Nonce");if(!StringUtils.hasText(sign)||!StringUtils.hasText(timestamp)){response.sendError(HttpServletResponse.SC_FORBIDDEN,"缺少安全参数");return;}// 2. 防重放 - 时间校验 (限制 60 秒内有效)longreqTime=Long.parseLong(timestamp);if(System.currentTimeMillis()-reqTime>60000){response.sendError(HttpServletResponse.SC_FORBIDDEN,"请求已过期");return;}// 3. 防重放 - Nonce 唯一性校验 (Redis)StringnonceKey="nonce:"+nonce;BooleanisAbsent=redisTemplate.opsForValue().setIfAbsent(nonceKey,"1",60,TimeUnit.SECONDS);if(Boolean.FALSE.equals(isAbsent)){response.sendError(HttpServletResponse.SC_FORBIDDEN,"重复的请求");return;}// 4. 防篡改 - 验签// 这里需要包装 HttpServletRequest 以便多次读取 Body (针对 POST/JSON)// 假设我们要对 URL 参数 + Header 参数进行签名校验Map<String,String>params=newHashMap<>();request.getParameterMap().forEach((k,v)->params.put(k,v[0]));params.put("timestamp",timestamp);params.put("nonce",nonce);StringcalculatedSign=SignatureUtils.generateSign(params);if(!calculatedSign.equals(sign)){response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"签名验证失败");return;}// 5. 放行给下游 (Spring Security)filterChain.doFilter(request,response);}}

注意:对于 POST JSON 请求,你需要实现HttpServletRequestWrapper来缓存 Body 流,否则 Filter 读完 Body 后,Controller 就读不到了。


🎯 总结

通过这一套组合拳,我们实现了:

  1. JWT:解决了“你是谁”的问题。
  2. 签名:解决了“数据被改”的问题。
  3. 时间戳+Nonce:解决了“请求被偷”的问题。

这基本达到了支付宝、微信支付等开放平台的安全标准。虽然增加了一些开发复杂度,但在资金安全面前,这一切都是值得的。

Next Step:
思考一下,如果你的密钥(SecretKey)泄露了怎么办?
进阶方案是引入RSA 非对称加密:客户端用私钥签名,服务端用公钥验签。这样即使服务端代码泄露,黑客也无法伪造客户端请求!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 14:42:12

为什么你的PHP视频转码总卡顿?3个被忽视的技术盲点曝光

第一章&#xff1a;PHP视频流实时转码的现状与挑战随着在线视频服务的迅猛发展&#xff0c;PHP作为广泛使用的Web开发语言之一&#xff0c;在处理视频流实时转码方面正面临前所未有的技术挑战。尽管PHP本身并非专为高性能音视频处理设计&#xff0c;但通过与FFmpeg等底层工具集…

作者头像 李华
网站建设 2026/6/10 10:00:28

如何用PHP快速对接AI图像识别服务?详解HTTP与gRPC两种方案

第一章&#xff1a;PHP 人工智能 图像识别接口在现代Web开发中&#xff0c;将人工智能能力集成到传统后端语言如PHP中&#xff0c;已成为提升应用智能化水平的重要手段。通过调用图像识别API&#xff0c;PHP应用可以实现物体检测、人脸识别、文字提取等功能&#xff0c;而无需从…

作者头像 李华
网站建设 2026/6/6 4:49:20

YOLOv8镜像内置htop/vim等系统工具

YOLOv8镜像内置htop/vim等系统工具的深度实践 在AI模型开发的真实世界里&#xff0c;我们常常面临这样的窘境&#xff1a;训练任务跑着跑着显存爆了&#xff0c;却只能干等着日志报错&#xff1b;想临时调个学习率&#xff0c;却发现容器里连个像样的编辑器都没有&#xff1b;J…

作者头像 李华
网站建设 2026/6/10 13:18:18

构建百万级并发视频流服务(基于PHP+SRS+FFmpeg的完整方案)

第一章&#xff1a;PHP 视频流实时转码处理的核心挑战在构建现代多媒体应用时&#xff0c;PHP 作为后端语言常需承担视频流的实时转码任务。尽管 PHP 本身并非专为高并发音视频处理设计&#xff0c;但在结合外部工具与合理架构的前提下&#xff0c;仍可实现高效的转码流程。然而…

作者头像 李华
网站建设 2026/6/10 13:25:29

【企业级PHP插件开发秘籍】:构建可复用插件系统的4大黄金法则

第一章&#xff1a;企业级PHP插件系统的设计理念构建企业级PHP插件系统的核心在于实现功能解耦、提升可维护性与支持动态扩展。一个良好的插件架构应允许第三方开发者在不修改核心代码的前提下&#xff0c;安全地注册、启用或禁用功能模块。松耦合与接口隔离 通过定义清晰的接口…

作者头像 李华
网站建设 2026/6/10 13:21:45

YOLOv8训练日志分析:定位过拟合与欠拟合问题

YOLOv8训练日志分析&#xff1a;定位过拟合与欠拟合问题 在目标检测的实际项目中&#xff0c;跑通一段训练代码往往只是第一步。真正决定模型能否上线的&#xff0c;是它在未知数据上的表现——而这背后&#xff0c;藏着一个老生常谈却又极易被忽视的问题&#xff1a;你的模型&…

作者头像 李华