API项目3:API签名认证
问题引入
我们为开发者提供了接口,却对调用者一无所知
假设我们的服务器只能允许 100 个人同时调用接口。如果有攻击者疯狂地请求这个接口,那是很危险的。一方面这可能会损害安全性,另一方面耗尽服务器性能,影响正常用户的使用。
因此我们必须为接口设置保护措施,例如限制每个用户每秒只能调用十次接口,即实施请求频次的限额控制。所以我们必须知道谁在调用接口,并且不能让无权限的人随意调用。
在我们之前开发后端时,我们会进行一些权限检查。例如,当管理员执行删除操作时,后端需要检查这个用户是否为管理员,直接从后端的 session 中获取的。但问题来了,比如我是前端直接发起请求,没有登录操作,没有输入用户名和密码,我怎么去调用呢?
API 签名认证
API 签名认证过程
签发签名 -> 使用签名或校验签名
为什么需要API签名认证
为了保证安全性,不能让任何人都能调用接口。
适用于无需保存登录态的场景。只认签名,不关注用户登录态(为了更通用)。
如何在后端实现签名认证?
需要两个东西,即 accessKey 和 secretKey,来标识用户
和用户名和密码类似,不过每次调用接口都需要带上,实现无状态的请求。
“无状态”指的是每个请求都是独立的,服务器不会保存客户端的任何状态信息。每次请求都包含所有必要的信息来完成该请求。这种设计使得系统更易于扩展和管理。
签发 accessKey 和 secretKey
一般来说,accessKey 和 secretKey 需要尽可能复杂无规律,防止黑客尝试破解,特别是密码。
签名认证实现
通过 http request header 头传递参数。
- 参数 1:accessKey:调用的标识 userA, userB(复杂、无序、无规律)
- 参数 2:secretKey:密钥(复杂、无序、无规律)该参数不能放到请求头中
- 参数 3:用户请求参数
- 参数 4:签名
加密方式:
对称加密、非对称加密、md5 签名(不可解密)
用户参数 + 密钥 => 签名生成算法(MD5、HMac、Sha1) => 不可解密的值
怎么知道这个签名对不对?
服务端用一模一样的参数和算法去生成签名,只要和用户传的的一致,就表示一致。
怎么防重放?
- 参数 5:加 nonce 随机数,只能用一次,(存在问题:服务端要保存使用过的随机数)
所以配合
- 参数 6:加 timestamp 时间戳,校验时间戳是否过期。
API 签名认证是一个很灵活的设计,具体要有哪些参数、参数名如何一定要根据场景来。
为什么需要两个 key?
如果仅凭一个 key 就可以调用接口,那么任何拿到这个 key 的人都可以无限制地调用这个接口。这就好比,为什么你在登录网站时需要输入密码,而不是只输入用户名就可以了?其实这两者的原理是一样的。如果像 token 一样,一个 key 不行吗?token 本质上也是不安全的,有可能会通过重放等等方式来攻破的。
TODO:关于 accessKey、secretKey 的生成方法,自行编写代码实现
实践:
在客户端 拿到 accessKey、secretKey
需要获取用户传递的 accessKey 和 secretKey。
对于这种数据,建议不要直接在 URL 中传递,而是选择在请求头中传递会更为妥当。
因为 GET 请求的 URL 存在最大长度限制,如果你传递的其他参数过多,可能会导致关键数据被挤出。
@PostMapping("/user")
public String getUserNameByPost(@RequestBody User user, HttpServletRequest request) {// 从请求头中获取名为 "accessKey" 的值String accessKey = request.getHeader("accessKey");// 从请求头中获取名为 "secretKey" 的值String secretKey = request.getHeader("secretKey");// 如果 accessKey 不等于 "sujie" 或者 secretKey 不等于 "abcdefgh"if (!accessKey.equals("sujie") || !secretKey.equals("abcdefgh")){// 抛出一个运行时异常,表示权限不足throw new RuntimeException("无权限");}// 如果权限校验通过,返回 "POST 用户名字是" + 用户名return "POST 用户名字是" + user.getUsername();
}
改造一下 SuApiClient.java,发请求可以带上 header,用这个就可以去添加很多的请求头
// 使用POST方法向服务器发送User对象,并获取服务器返回的结果public String getUserNameByPost(@RequestBody User user) {// 将User对象转换为JSON字符串String json = JSONUtil.toJsonStr(user);// 使用HttpRequest工具发起POST请求,并获取服务器的响应HttpResponse httpResponse = HttpRequest.post("http://localhost:8123/api/name/user")// 添加前面构造的请求头.addHeaders(getHeaderMap()).body(json) // 将JSON字符串设置为请求体.execute(); // 执行请求// 打印服务器返回的状态码System.out.println(httpResponse.getStatus());// 获取服务器返回的结果String result = httpResponse.body();// 打印服务器返回的结果System.out.println(result);// 返回服务器返回的结果return result;}// 创建一个私有方法,用于构造请求头private Map<String, String> getHeaderMap() {// 创建一个新的 HashMap 对象Map<String, String> hashMap = new HashMap<>();// 将 "accessKey" 和其对应的值放入 map 中hashMap.put("accessKey", accessKey);// 将 "secretKey" 和其对应的值放入 map 中hashMap.put("secretKey", secretKey);// 返回构造的请求头 mapreturn hashMap;}
安全传递
存在的问题:
我们的请求有可能被人拦截,我们将密码放在请求头中,如果有中间人拦截到了你的请求,他们就可以直接从请求头中获取你的密码,然后使用你的密码发送请求。
密码绝对不能传递。也就是说,在向对方发送请求时,密码绝对不能以明文的方式传递,必须通过特殊的方式进行传递。
我们需要对该密码进行加密,这里通常称之为签名。
可以将用户传递的参数与该密钥拼接在一起,然后使用单向签名算法进行加密。
如何防止重放请求有两种方式可以考虑:
第一种方式是通过加入一个随机数实现标准的签名认证。每次请求时,发送一个随机数给后端。后端只接受并认可该随机数一次,一旦随机数被使用过,后端将不再接受相同的随机数。这种方式解决了请求重放的问题,因为即使对方使用之前的时间和随机数进行请求,后端会认识到该请求已经被处理过,不会再次处理。然而,这种方法需要后端额外开发来保存已使用的随机数。并且,如果接口的并发量很大,每次请求都需要一个随机数,那么可能会面临处理百万、千万甚至亿级别请求的情况。因此,除了使用随机数之外,我们还需要其他机制来定期清理已使用的随机数。
第二种方式是加入一个时间戳(timestamp)。每个请求在发送时携带一个时间戳,并且后端会验证该时间戳是否在指定的时间范围内,例如不超过10分钟或5分钟。这可以防止对方使用昨天的请求在今天进行重放。通过这种方式,我们可以一定程度上控制随机数的过期时间。因为后端需要同时验证这两个参数,只要时间戳过期或随机数被使用过,后端会拒绝该请求。因此,时间戳可以在一定程度上减轻后端保存随机数的负担。通常情况下,这两种方法可以相互配合使用。
因此,在标准的签名认证算法中,建议至少添加以下五个参数:accessKey、secretKey、sign、nonce(随机数)、timestamp(时间戳)。此外,建议将用户请求的其他参数,例如接口中的 name 参数,也添加到签名中,以增加安全性。
安全传递实现
新建一个 utils 包
,在 utils 包下新建 SignUtils.java
(签名工具)
这个 hashmap 还需要进行拼接,我们传递的是用户的这些参数,但其实没有必要传递那么多参数,直接将 body 作为参数传递进来(在这里,我们也可以传递 hashmap,只要有一些共同的参数,能让客户端和服务端之间保持一致即可)。
/*** 签名工具*/
public class SignUtils {/*** 生成签名* @param hashMap 包含需要签名的参数的哈希映射* @param secretKey 密钥* @return 生成的签名字符串*/public static String genSign(Map<String, String> hashMap, String secretKey) {// 使用SHA256算法的DigesterDigester md5 = new Digester(DigestAlgorithm.SHA256);// 构建签名内容,将哈希映射转换为字符串并拼接密钥String content = hashMap.toString() + "." + secretKey;// 计算签名的摘要并返回摘要的十六进制表示形式return md5.digestHex(content);}
}
刚刚客户端只有这两个参数 accessKey、secretKey
现在再加几个参数
/*** 获取请求头的哈希映射* @param body 请求体内容* @return 包含请求头参数的哈希映射*/
private Map<String, String> getHeaderMap(String body) {Map<String, String> hashMap = new HashMap<>();hashMap.put("accessKey", accessKey);// 注意:不能直接发送密钥// hashMap.put("secretKey", secretKey);// 生成随机数(生成一个包含100个随机数字的字符串)hashMap.put("nonce", RandomUtil.randomNumbers(4));// 请求体内容hashMap.put("body", body);// 当前时间戳// System.currentTimeMillis()返回当前时间的毫秒数。通过除以1000,可以将毫秒数转换为秒数,以得到当前时间戳的秒级表示// String.valueOf()方法用于将数值转换为字符串。在这里,将计算得到的时间戳(以秒为单位)转换为字符串hashMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));// 生成签名hashMap.put("sign", genSign(body, secretKey));return hashMap;
}/*** 通过POST请求获取用户名* @param user 用户对象* @return 从服务器获取的用户名*/
public String getUserNameByPost(@RequestBody User user) {// 将用户对象转换为JSON字符串String json = JSONUtil.toJsonStr(user);HttpResponse httpResponse = HttpRequest.post("http://localhost:8123/api/name/user")// 添加请求头.addHeaders(getHeaderMap(json))// 设置请求体.body(json)// 发送POST请求.execute();// 打印响应状态码System.out.println(httpResponse.getStatus());// 打印响应体内容String result = httpResponse.body();System.out.println(result);return result;
}
接下来服务端
@PostMapping("/user")public String getUserNameByPost(@RequestBody User user, HttpServletRequest request) {// 1.拿到这五个我们可以一步一步去做校验,比如 accessKey 我们先去数据库中查一下// 从请求头中获取参数String accessKey = request.getHeader("accessKey");String nonce = request.getHeader("nonce");String timestamp = request.getHeader("timestamp");String sign = request.getHeader("sign");String body = request.getHeader("body");// 不能直接获取秘钥// String secretKey = request.getHeader("secretKey");// TODO 2.校验权限,这里模拟一下,直接判断 accessKey 是否为"yupi",实际应该查询数据库验证权限if (!accessKey.equals("sujie")){throw new RuntimeException("无权限");}// TODO 3.校验一下随机数,因为时间有限,就不带大家再到后端去存储了,后端存储用hashmap或redis都可以// 校验随机数,模拟一下,直接判断nonce是否大于10000if (Long.parseLong(nonce) > 10000) {throw new RuntimeException("无权限");}// TODO 4.校验时间戳与当前时间的差距,交给大家自己实现//// if (timestamp) {}// TODO 5. 从实际数据库中取得用户secretKeyString serverSign = SignUtils.genSign(body, "abcdefgh");if (!serverSign.equals(sign)) {throw new RuntimeException("无权限");}return "POST 用户名字是" + user.getUsername();}
整个签名认证算法的流程就是这样
需要强调的是,API签名认证是一种非常灵活的设计,具体需要哪些参数以及参数名的选择都应根据具体场景来确定。尽量避免在前端进行签名认证,而是由服务端来处理
例如,某些公司或项目的签名认证可能会包含 userId 字段以区分用户。还可能包含 appId 和 version 字段来表示应用程序的版本号。有时还会添加一些固定的盐值等等。
相关文章:

API项目3:API签名认证
问题引入 我们为开发者提供了接口,却对调用者一无所知 假设我们的服务器只能允许 100 个人同时调用接口。如果有攻击者疯狂地请求这个接口,那是很危险的。一方面这可能会损害安全性,另一方面耗尽服务器性能,影响正常用户的使用。…...

unity学习-Directional light光的设置
ccColor:环境光的颜色 Mode:灯光模式,Realtime(实时光影),实时计算光影,消耗性能但是效果好,Baked烘焙光影,将光的照射效果作为贴图贴在静态的物体上形成一种虚假的光照…...

简单实现通过电脑操作手机
通过电脑操作手机,支持单击,拖抓事件,延时有1-2秒。 具体步骤: 1、从手机截图到sdcard 2、将图片导出到PC 3、从PC加载展示图片 4、开启定时器 5、设置点击、滚动事件 1、 private static void takeScreenshot(String path)…...

基于ESP32的便携式游戏机
基于ESP32的便携式游戏机 一、项目说明二、项目材料三、程序测试四、设置LCD屏幕五、控制设置六、测试电路七、外壳制作八、结果 视频: ESP32 pro 一、项目说明 欢迎来到复古游戏的世界!你是否曾经想要以便携格式重温童年的经典游戏?在这个…...

【LeetCode 88. 合并两个有序数组】 java实现
LeetCode 88. 合并两个有序数组 题目描述 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。你可以假设 nums1 的大小等于 m n(即…...

200Kg大载重多旋无人机价格高昂技术分析
200Kg大载重多旋无人机作为一种高度专业化的航空工具,其价格相较于普通无人机显著较高,这主要是由于其在技术设计和生产过程中所需的高要求所致。以下是对其价格高昂的技术分析: 一、高性能材料与结构设计 1. 高强度轻量化材料:…...

快速理解http的get和post
在网络通信中,HTTP 协议扮演着非常重要的角色,而不同的 HTTP 方法决定了客户端与服务器之间的交互方式。 这里讲一下最常用的两种方法——GET 和 POST。 一、GET 方法 GET 方法用于从服务器获取资源。 这就像去图书馆借书——你向图书馆请求一本特定的…...

Mamba学习笔记(3)—S4原理基础
文章目录 Efficiently Modeling Long Sequences with Structured State Spaces0 Abstract1 Introduction2 Background:State Spaces2.1 State Space Models: A Continuous-time Latent State Model2.2 Addressing Long-Range Dependencies with HiPPO2.3 Discrete-t…...

好看的ppt字体推荐!分享3个制作幻灯片的常用软件!
ppt什么字体好看? 好看是一个比较主观的概念,见仁见智,在选用ppt字体时,比起关注好看,字体是否“合适”应该是优先级更高的需求。这里的合适,即PPT所选用字体的风格、呈现效果是否与PPT的主题和使用场景相…...

第6篇:无线与移动网络
目录 引言 6.1 无线网络的基础概念 6.2 无线局域网(WLAN)与IEEE 802.11 6.3 蓝牙与无线个域网(WPAN) 6.4 无线城域网(WMAN)与WiMax 6.5 ZigBee与智能家居 6.6 移动蜂窝网络(3G/4G/5G&…...

【C++标准模版库】unordered_map和unordered_set的介绍及使用
unordered_map和unordered_set 一.unordered_set1.unordered_set类的介绍2.unordered_set和set的使用差异 二.unordered_map1.unordered_map和map的使用差异 三.unordered_multimap/unordered_multiset四.unordered_map/unordered_set的哈希相关接口 一.unordered_set 1.unord…...

深度解析Transformer:从自注意力到MLP的工作机制
深度解析Transformer:从自注意力到MLP的工作机制 以下大部分内容本来自对3Blue1Brown的视频讲解的整理组织 一、Transformer的目标 为了简化问题,这里讨论的模型目标是接受一段文本,预测下一个词。这种任务对模型提出了两大要求:…...

《米小圈动画成语》|在趣味中学习,在快乐中掌握成语知识!
作为一名家长,我一直希望孩子能够在学习的过程中既感受到乐趣,又能获得真正的知识。成语作为中华文化的精华,虽然意义深远、简洁凝练,但对于一个小学生来说,学习和理解这些言简意赅的成语无疑是一个挑战。尤其是有些成…...

linux系统之jar启动脚本
编辑linux启动脚本 执行 vi run_blog 按i 进入编辑,复制以下代码,并根据当前环境修改三个参数。以下是详细完整脚本代码: #!/bin/bash# 配置部分 JAR_PATH"/path/to/your/app.jar" # 替换为你的 JAR 文件的实际路径 L…...

简单认识Maven 2-Maven坐标
Maven坐标 在 Maven 中,坐标(Coordinates)用于唯一标识一个项目或依赖项,就像在现实世界中通过经纬度来确定一个地理位置一样。Maven 坐标由三个主要部分组成:groupId、artifactId 和 version。 groupId(…...

Xilinx UltraScale系列FPGA纯verilog图像缩放,工程项目解决方案,提供2套工程源码和技术支持
目录 1、前言工程概述免责声明FPGA高端图像处理培训 2、相关方案推荐我这里已有的FPGA图像缩放方案本方案在Xilinx Artix7 系列FPGA上的应用本方案在Xilinx Kintex7 系列FPGA上的应用本方案在Xilinx Zynq7000 系列FPGA上的应用本方案在国产FPGA紫光同创系列上的应用本方案在国产…...

React(二) JSX中的this绑定问题;事件对象参数传递;条件渲染;列表渲染;JSX本质;购物车案例
文章目录 一、jsx事件绑定1. 回顾this的绑定方式2. jsx中的this绑定问题(1) 方式一:bind绑定(2) 方式二:使用 ES6 class fields 语法(3) 方式三:直接传入一个箭头函数(重要) 3.事件参数传递(1) 传递事件对象event(2) 传递其他参数 4. 事件绑定…...

前端开发攻略---取消已经发出但是还未响应的网络请求
目录 注意: 1、Axios实现 2、Fetch实现 3、XHR实现 注意: 当请求被取消时,只会本地停止处理此次请求,服务器仍然可能已经接收到了并处理了该请求。开发时应当及时和后端进行友好沟通。 1、Axios实现 <!DOCTYPE html> &…...

韩信走马分油c++
韩信走马分油c 题目算法代码 题目 把油桶里还剩下的10斤油平分,只有一个能装3斤的油葫芦和一个能装7斤的瓦罐。如何分。 算法 油壶编号0,1,2。不同倒法有:把油从0倒进0(本壶到本壶,无效)&…...

【Linux】Anaconda下载安装配置Pytorch安装配置(保姆级)
目录 Anaconda下载 Anaconda安装 conda init conda --v Conda 配置 conda 环境创建 conda info --envs conda list Pytorch安装配置 检验安装情况 检验是否可以使用GPU Anaconda下载 可以通过两种途径完成Anaconda安装包的下载 途径一:本地windows下…...

渗透测试导论
渗透测试的定义和目的 渗透测试(Penetration Testing)是一项安全演习,网络安全专家尝试查找和利用计算机系统中的漏洞。 模拟攻击的目的是识别攻击者可以利用的系统防御中的薄弱环节。 这就像银行雇用别人假装盗匪,让他们试图闯…...

鸿蒙学习笔记--搭建开发环境及Hello World
文章目录 一、概述二、开发工具下载安装2.1 下载开发工具DevEco Studio NEXT2.2 安装DevEco Studio 三、启动软件四、第一个应用Hello World4.1 创建应用4.2 创建模拟器4.3 开启Hyper-v功能4.4 启动虚拟机 剑子仙迹 诗号:何须剑道争锋?千人指,…...

【ArcGIS风暴】ArcGIS字段计算器公式汇总
在GIS数据处理中,ArcGIS的字段计算器是一个强大的工具,它可以帮助我们进行各种数值计算、文本处理和逻辑判断。本文将为您整合和分类介绍ArcGIS字段计算器中的常用公式,并通过实例说明它们的应用。 文章目录 一、数值计算类二、文本处理类三、日期和时间类四、逻辑判断类五、…...

探索秘境:如何使用智能体插件打造专属的小众旅游助手『小众旅游探险家』
文章目录 摘要引言智能体介绍和亮点展示介绍亮点展示 已发布智能体运行效果智能体创意想法创意想法创意实现路径拆解 如何制作智能体可能会遇到的几个问题快速调优指南总结未来展望 摘要 本文将详细介绍如何使用智能体平台开发一款名为“小众旅游探险家”的旅游智能体。通过这…...

机械臂力控方法概述(一)
目录 1. MoveIt 适用范围 2. 力控制框架与 MoveIt 的区别 3. 力控方法 3.1 直接力控制 (Direct Force Control) 3.2 间接力控制 (Indirect Force Control) 3.2.1 柔顺控制 (Compliant Control) 3.2.2 阻抗控制 (Impedance Control) 3.2.3 导纳控制 (Admittance Control…...

1971. 寻找图中是否存在路径
有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接&#x…...

FLINK SQL语法(1)
DDL Flink SQL DDL(Data Definition Language)是Flink SQL中用于定义和管理数据结构和数据库对象的语法。以下是对Flink SQL DDL的详细解析: 一、创建数据库(CREATE DATABASE) 语法:CREATE DATABASE [IF…...

【Fargo】1:基于libuv的udp收发程序
开发UDP处理程序 我正在开发一个基于libuv的UDP发送/接收程序,区分发送端和接收端,设计自定义包数据结构,识别和处理丢包和乱序。 创建项目需求 用户正在要求一个使用libuv的C++程序,涉及UDP发送和接收,数据包包括序列号和时间戳,接收端需要检测丢包和乱序包。 撰写代…...

WebSocket介绍和入门案例
目录 一、WebSocket 详解1. 定义与特点:2. 工作原理:3. 应用场景: 二、入门案例 一、WebSocket 详解 1. 定义与特点: WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时、双向的数据传…...

k8s集群版本升级
Kubernetes 集群版本升级是为了获得最新的功能、增强的安全性和性能改进。然而,升级过程需要谨慎进行,特别是在生产环境中。通常,Kubernetes 集群的版本升级应遵循逐步升级的策略,不建议直接跳过多个版本。 Kubernetes 版本升级的…...