对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)
前言
一个web系统,从接口的使用范围也可以分为对内和对外两种,对内的接口主要限于一些我们内部系统的调用,多是通过内网进行调用,往往不用考虑太复杂的鉴权操作。但是,对于对外的接口,我们就不得不重视这个问题,外部接口没有做鉴权的操作就直接发布到互联网,而这不仅有暴露数据的风险,同时还有数据被篡改的风险,严重的甚至是影响到系统的正常运转
方案一:Spring Boot+Aop+注解实现Api接口签名验证
方案二:在已有接口上,拦截器拦截,接口路径,白名单匹配
Spring Boot+Aop+注解实现Api接口签名验证
appId和secret+token+时间戳
token的生成过程中在加入时间戳,校验token正确性之前先校验时间戳是否在一定时间窗口内(比如说1分钟),如果超过一分钟,直接拒绝请求,通过后再校验token。
新接口加上注解
由于项目需要开发第三方接口给多个供应商,为保证Api接口的安全性,遂采用Api接口签名验证。
一、为什么需要 API 接口签名
对外开放的 API 接口都会面临一些安全问题,例如伪装攻击、篡改攻击、重放攻击以及数据信息泄漏的风险。利用 API 接口签名能方便的防范这些安全问题和风险。在设计 API 接口签名时主要考虑以下几点:
请求发起时间得在限制范围内
请求的用户是否真实存在
是否存在重复请求
请求参数是否被篡改
1.保证请求数据正确
当请求中的某一个字段的值变化时,原有的签名结果就会发生变化。所以,只要参数发生变化,签名就要发生变化,否则请求将会是一个无效的请求。
2.保证请求来源合法
一般情况下,生成签名的算法都会成对出现一个 appKey 和一个 appSecret,根据 appKey 能识别出调用者身份;根据 appSecret 能识别出签名是否合法。
3.识别接口的时效性
一般情况下,签名和参数中会包含时间戳,这样服务端就可以验证客户端请求是否在有效时间内,从而避免接口被长时间的重复调用
4.是否存在重复请求
API 接口签名验签实现机制
签名验签流程图

1 客户端向服务端申请 appKey,appSecret ,服务端下发 appKey,appSecret。
2 客户端集成 SDK 产生 sign,将 appKey,请求参数,时间戳,sign,随机数nonce 发送到服务端,服务端根据请求参数使用 SDK 中的签名规则生成签名来验证sign的合法性,之后返回结果。
实现思路:
我们按照主要防御措施先后顺序来实现,首先已知我们得到以下四个参数:
// 供应商的id,验证用户的真实性
String appid = request.getHeader("appid");
// 请求发起的时间
String timestamp = request.getHeader("timestamp");
// 随机数
String nonce = request.getHeader("nonce");
// 签名算法生成的签名
String sign = request.getHeader("sign");1.请求发起时间得在限制范围内
像这种比较简单,就是获取服务器的当前时间去跟请求发起时间比较。
// 限制为(含)60秒以内发送的请求
long time = 60;
long now = System.currentTimeMillis() / 1000;
if (now - Long.valueOf(timestamp) > time) {return ObjectResponse.fail("请求发起时间超过服务器限制时间");
}
2.请求的用户是否真实存在
一般会有以下两个场景
场景一:在前后端分离的模式中,用户登录后得到token,用户调用接口时传递token来确保用户的真实性。
场景二:接口调用方不需要登录,那么我们接口提供方可以提供appid(调用时需要传递)与secret(在签名算法中使用)给接口调用方来验证用户的真实性。
这里我主要说一下场景二,如下:
// 查询appid是否正确来验证用户的真实性
CoreApiKey apiKey = coreApiKeyService.selectByAppid(appid);
if (apiKey == null) {return ObjectResponse.fail("appid参数错误");
}
- 是否存在重复请求
这里利用nonce参数,每次请求时先判断nonce在redis是否存在,存在则认为是重复请求,不存在就存放到redis中。但是这会有一个问题,随着请求的 次数越来越多,那么redis存放的nonce集合会越来越大,这肯定不是我们所期望的。这时我们可以巧妙的利用在请求发起时间得在限制范围内中的time(服务器限制60秒以内发生的请求),因为此步骤主要是验证请求是否重复,如果timestamp时间戳变了,那就不是重复请求了,所以我们可以在nonce存放到redis时给它设置一个过期时间(60秒),这样既保证了nonce的唯一性也不会发生nonce集合的无限大。
// 验证请求是否重复
if (redisService.hasKeyHashItem("third_party_key", apiKey.getAppid() + nonce)) {return ObjectResponse.fail("请不要发送重复的请求");
} else {// 如果nonce没有存在缓存中,则加入,并设置失效时间(秒)redisService.setHashItem("third_party_key", apiKey.getAppid() + nonce, nonce, time);
}
- 请求参数是否被篡改
利用签名算法来生成签名。主要就是接口调用方的签名算法必须与接口提供方的签名算法一致。签名算法可以自己捣鼓捣鼓,我这里是先对key进行字典序排序,然后以url的参数格式进行拼接(secret在最后拼接),最后进行md5加密,以下一个Api接口签名验证就大功告成啦!
JSONObject signObj = new JSONObject();
signObj.put("appid", appid);
signObj.put("timestamp", timestamp);
signObj.put("nonce", nonce);
String mySign = getSign(signObj, apiKey.getSecret());
// 验证签名
if (!mySign.equals(sign)) {return ObjectResponse.fail("签名信息错误");
}/*** 获取签名信息* @param data* @param secret* @return*/
private static String getSign(JSONObject data, String secret) {// 由于map是无序的,这里主要是对key进行排序(字典序)Set<String> keySet = data.keySet();String[] keyArr = keySet.toArray(new String[keySet.size()]);Arrays.sort(keyArr);StringBuilder sbd = new StringBuilder();for (String k : keyArr) {if (StringUtil.isNotEmpty(data.getString(k))) {sbd.append(k + "=" + data.getString(k) + "&");}}// secret最后拼接sbd.append("secret=").append(secret);return MD5Util.encode(sbd.toString());
}5.基于SringBoot以及Redis使用Aop来实现Api接口签名验证的源码
@Component
@Aspect
@Slf4j
public class ThridPartyApiAspect {@Autowiredprivate HttpServletRequest request;@Autowiredprivate HttpServletResponse response;@Autowiredprivate RedisService redisService;@Autowiredprivate CoreApiKeyService coreApiKeyService;/*** 表示匹配带有自定义注解的方法*/@Pointcut("@annotation(com.stan.framework.anno.ThridPartyApi)")public void pointcut() {}@Around("pointcut()")public Object around(ProceedingJoinPoint point) {try {// 供应商的id,验证用户的真实性String appid = request.getHeader("appid");// 请求发起的时间String timestamp = request.getHeader("timestamp");// 随机数String nonce = request.getHeader("nonce");// 签名算法生成的签名String sign = request.getHeader("sign");if (StringUtil.isEmpty(appid) || StringUtil.isEmpty(timestamp) || StringUtil.isEmpty(nonce) || StringUtil.isEmpty(sign)) {return ObjectResponse.fail("请求头参数不能为空");}// 限制为(含)60秒以内发送的请求long time = 60;long now = System.currentTimeMillis() / 1000;if (now - Long.valueOf(timestamp) > time) {return ObjectResponse.fail("请求发起时间超过服务器限制时间");}// 查询appid是否正确CoreApiKey apiKey = coreApiKeyService.selectByAppid(appid);if (apiKey == null) {return ObjectResponse.fail("appid参数错误");}// 验证请求是否重复if (redisService.hasKeyHashItem("third_party_key", apiKey.getAppid() + nonce)) {return ObjectResponse.fail("请不要发送重复的请求");} else {// 如果nonce没有存在缓存中,则加入,并设置失效时间(秒)redisService.setHashItem("third_party_key", apiKey.getAppid() + nonce, nonce, time);}JSONObject signObj = new JSONObject();signObj.put("appid", appid);signObj.put("timestamp", timestamp);signObj.put("nonce", nonce);String mySign = getSign(signObj, apiKey.getSecret());// 验证签名if (!mySign.equals(sign)) {return ObjectResponse.fail("签名信息错误");}try {return point.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}} catch (Exception e) {e.printStackTrace();return ObjectResponse.fail("解析请求参数异常");}return null;}/*** 获取签名信息* @param data* @param secret* @return*/private static String getSign(JSONObject data, String secret) {// 由于map是无序的,这里主要是对key进行排序(字典序)Set<String> keySet = data.keySet();String[] keyArr = keySet.toArray(new String[keySet.size()]);Arrays.sort(keyArr);StringBuilder sbd = new StringBuilder();for (String k : keyArr) {if (StringUtil.isNotEmpty(data.getString(k))) {sbd.append(k + "=" + data.getString(k) + "&");}}// secret最后拼接sbd.append("secret=").append(secret);return MD5Util.encode(sbd.toString());}
}6. 测试签名
/*** @Author lc* @description:* @Date 2022/4/26 15:19* @Version 1.0*/@RestController
@Api(tags = "对外接口")
@RequestMapping("/test")
public class ThirdPartyApiAspectController {@ThirdPartyApi@ApiOperation("对接接口测试")@PostMapping(value = "/test")public ResponseVO<String> test(HttpServletRequest request){return new ResponseVO ("签名校验");}
}
相关文章:
对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)
前言 一个web系统,从接口的使用范围也可以分为对内和对外两种,对内的接口主要限于一些我们内部系统的调用,多是通过内网进行调用,往往不用考虑太复杂的鉴权操作。但是,对于对外的接口,我们就不得不重视这个…...
微服务-理论(CAP,一致性协议)
CAP理论 关于CAP理论的介绍可以直接看这篇文章 CAP分别是什么? 一致性(Consistency 一致性包括强一致性,弱一致性,最终一致性。 一致性其实是指数据的一致性,为什么数据会不一致呢? 如上面这张图&…...
CTFshow web入门web128-php特性31
开启环境: 一个新的姿势,当php扩展目录下有php_gettext.dll时: _()是一个函数。 _()gettext() 是gettext()的拓展函数,开启text扩展get_defined_vars — 返回由所有已定义变量所组成的数组。 call_user_func — 把第一个参数作为回调函数调…...
再见2023,你好2024(附新年烟花python实现)
亲爱的朋友们: 写点什么呢,我已经停更两个月了。2023年快结束了,时间真的过得好快,总要写点什么留下纪念吧。这一年伴随着许多挑战和机会,给了我无数的成长和体验。坦白说,有时候我觉得自己好像是在时间的…...
Redis 的常用命令
一、Redis 通用命令 TYPE key:返回 key 所储存的值的类型。 OBJECT ENCODING key:返回key所储存的值的底层编码方式。 DEL key:该命令用于在 key 存在时删除 key。 EXPIRE key seconds:设置指定key的过期时间。 RENAME key newke…...
【模拟电路】模拟集成电路之神-NE555
一、集成电路NE555简介 二、功能框图与引脚说明 三、比较器(运放) 四、反相门(非门) 五、或非门 六、双稳态触发器 七、NE555的工作原理 集成电路NE555的芯片手册 C5157696 一、集成电路NE555简介 NE555起源于上个世纪70年代&a…...
收集最新的 Sci-Hub 网址(本文章持续更新2024)
自用收集最新的 Sci-Hub 网址 本文章持续更新收集 Sci-Hub 的可用网址链接仅供交流学习使用,如对您有所帮助,请收藏并推荐给需要的朋友,由于网站限制,不一定所有网址都能在您所在的位置访问,通常情况下,一…...
针对NPC客户端的升级(脚本执行)
上一次我们使用NPS自动注册的方式,在被控端上实现了自动创建NPC客户端链接。 Linux主机自动注册NPS客户端(脚本化) 但是在使用过程中我发现存在很多的问题,如果被控端重启客户端或者出现了多个NPS时会造成冲突,所以考虑…...
[每周一更]-(第51期):Go的调度器GMP
参考文献 https://learnku.com/articles/41728http://go.cyub.vip/gmp/gmp-model.html#g-m-phttps://blog.csdn.net/ByteDanceTech/article/details/129292683https://www.ququ123.top/2022/04/golang_gmp_principle/ 什么是GMP? GMP模型是Go语言并发模型的核心概念&#x…...
阿里云和腾讯云服务器系统盘40G或50G空间够用吗?
云服务器系统盘40G或50G空间够用吗?够用,操作系统一般占用几个GB的存储空间,尤其是Linux操作系统占用空间容量更小,阿里云和腾讯云服务器系统盘默认提供的40GB高效云盘或50G通用型SSD云硬盘,阿腾云atengyun.com分享是否…...
网络层协议 ——— IP协议
文章目录 IP协议基本概念IP协议格式分片与组装网段划分特殊的IP地址IP地址的数量限制私网IP地址和公网IP地址路由路由表生成算法 IP协议 IP协议全称为“网际互连协议(Internet Protocol)”,IP协议是TCP/IP体系中的网络层协议。 基本概念 网…...
MATLAB --- interp1( )函数的用法
interp1() 是 MATLAB 中用于一维插值的函数, 它可以根据给定的数据点进行插值,从而在给定的插值点处估计函数的值 下面是 interp1() 函数的用法: Vq interp1(X, V, Xq) Vq interp1(X, V, Xq, method) Vq interp1(X, V, Xq, method, extr…...
【react-taro-canvas】用canvas手写一个数字、字母混合的行为验证码
用canvas手写一个数字、字母混合的行为验证码 实现效果源码 实现效果 源码 import Taro from "tarojs/taro"; import { View, Canvas, Input, Button } from "tarojs/components"; import { useState, useEffect } from "react"; // 画随机线函…...
ctfshow——信息搜集
文章目录 web 1web 2web 3web 4web 5web 6web 7web 8web 9web 10web 11web 12web 13web 14web 15web 16web 17web 18web 19web 20 web 1 题目提示开发注释未及时删除。 直接右键查看源代码。 web 2 在这关我们会发现:1)无法使用右键查看源代码&…...
【Linux驱动】设备树模型的LED驱动 | 查询方式的按键驱动
🐱作者:一只大喵咪1201 🐱专栏:《Linux驱动》 🔥格言:你只管努力,剩下的交给时间! 目录 🍮设备树模型的LED驱动🍩设备树文件🍩驱动程序 …...
GZ075 云计算应用赛题第4套
2023年全国职业院校技能大赛(高职组) “云计算应用”赛项赛卷4 某企业根据自身业务需求,实施数字化转型,规划和建设数字化平台,平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”,拟采用开源OpenSt…...
小型肉制品厂废水处理设备加工厂家
诸城市鑫淼环保小编带大家了解一下小型肉制品厂废水处理设备加工厂家 在小型肉制品厂,处理肉类加工废水是非常重要的环保问题。废水中含有蛋白质、脂肪、悬浮物和有机物等,需要进行合适的处理以减少对环境的污染。以下是一些常见的小型肉制品厂废水处理设…...
SpringBoot整合ElasticSearch实现CRUD操作
本文来说下SpringBoot整合ES实现CRUD操作 文章目录 概述项目搭建ES简单的crud操作本文小结 概述 SpringBoot支持两种技术和es交互。一种的jest,还有一种就是SpringData-ElasticSearch。根据引入的依赖不同而选择不同的技术。反正作为spring全家桶,目前是…...
香橙派--关于jammy-xfce-arm64.f12a43b3e629442a073a7236bf9166ce.tar.lz4的rootfs定制与镜像制作
使用 x64 的 Ubuntu22.04 电脑编译 Linux SDK,即 orangepi-build,支持在安装有 Ubuntu 22.04 的电脑上运行,所以下载 orangepi-build 前,请首先确保自己电脑已安装的 Ubuntu 版本是 Ubuntu22.04。查看电脑已安装的 Ubuntu 版本的命…...
前端八股文(HTML篇)一
目录 1.什么是DOCTYPE,有何用呢? 2.说说对html语义化的理解 3.src和href的区别? 4.title与h1的区别,b与strong的区别,i与em的区别? 5.什么是严格模式与混杂模式? 6.前端页面有哪三层构成,分…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
