API项目3:API签名认证

问题引入

我们为开发者提供了接口,却对调用者一无所知

假设我们的服务器只能允许 100 个人同时调用接口。如果有攻击者疯狂地请求这个接口,那是很危险的。一方面这可能会损害安全性,另一方面耗尽服务器性能,影响正常用户的使用。

因此我们必须为接口设置保护措施,例如限制每个用户每秒只能调用十次接口,即实施请求频次的限额控制。所以我们必须知道谁在调用接口,并且不能让无权限的人随意调用。

在我们之前开发后端时,我们会进行一些权限检查。例如,当管理员执行删除操作时,后端需要检查这个用户是否为管理员,直接从后端的 session 中获取的。但问题来了,比如我是前端直接发起请求,没有登录操作,没有输入用户名和密码,我怎么去调用呢?

API 签名认证

API 签名认证过程

签发签名  -> 使用签名或校验签名

为什么需要API签名认证

为了保证安全性,不能让任何人都能调用接口。

适用于无需保存登录态的场景。只认签名,不关注用户登录态(为了更通用)。

如何在后端实现签名认证?

需要两个东西,即 accessKeysecretKey,来标识用户

和用户名和密码类似,不过每次调用接口都需要带上,实现无状态的请求。

“无状态”指的是每个请求都是独立的,服务器不会保存客户端的任何状态信息。每次请求都包含所有必要的信息来完成该请求。这种设计使得系统更易于扩展和管理。

签发 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 字段来表示应用程序的版本号。有时还会添加一些固定的盐值等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/450498.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第492题构造矩形

题目&#xff1a; 题解&#xff1a; func constructRectangle(area int) []int {w : int(math.Sqrt(float64(area)))for area%w > 0 {w--}return []int{area / w, w} }

DeBiFormer:带有可变形代理双层路由注意力的视觉Transformer

https://arxiv.org/pdf/2410.08582v1 摘要 带有各种注意力模块的视觉Transformer在视觉任务上已表现出卓越的性能。虽然使用稀疏自适应注意力&#xff08;如在DAT中&#xff09;在图像分类任务中取得了显著成果&#xff0c;但在对语义分割任务进行微调时&#xff0c;由可变形…

【论文精读】RELIEF: Reinforcement Learning Empowered Graph Feature Prompt Tuning

RELIEF: Reinforcement Learning Empowered Graph Feature Prompt Tuning 前言AbstractMotivationSolutionRELIEFIncorporating Feature Prompts as MDPAction SpaceState TransitionReward Function Policy Network ArchitectureDiscrete ActorContinuous ActorCritic Overall…

Firefox火狐浏览器打开B站视频时默认静音

文章目录 环境问题解决办法 环境 Windows 11家庭版Firefox浏览器 131.0.2 (64 位) 问题 用Firefox浏览器打开B站的视频时&#xff0c;默认是静音播放的&#xff1a; 而其它浏览器&#xff0c;比如Chrome和Edge&#xff0c;默认是带声音播放的。 虽然不是什么大问题&#xf…

二叉树与堆讲解

目录 1.树的概念及结构 1.树的概念 2.树的相关概念 3.树的表示 2.二叉树 1.概念 2.特殊的二叉树 1.满二叉树 2.完全二叉树 3.二叉树的性质 4.二叉树的存储结构 1.顺序结构 2.链式存储 3.堆 1.堆的概念及结构 2.堆的实现 1.堆的创建 2.堆的初始化&#xff08;H…

Javascript算法——双指针法移除元素、数组去重、比较含退格字符、有序数组平方

数组移除元素&#xff08;保证数组仍连续&#xff09; 暴力求解法&#xff08;两层for循环&#xff09;,length单词拼写错误❌二次嵌套for的length设置 /*** param {number[]} nums* param {number} val* return {number}*/ var removeElement function(nums, val) {let leng…

三、账号密码存储

使用Playfers存储 Unity本地持久化类Playerprefs使用详解 - PlaneZhong - 博客园 (cnblogs.com) 一、登陆界面切换 1、登陆界面的脚本&#xff08;机制类脚本&#xff09; 在这个UI上挂载一个脚本LoginWnd 先声明一下这个脚本&#xff0c;拖拽 2、在登录模块中调用 这里的l…

手写Spring IOC-简易版

目录 项目结构entitydaoIUserDaoUserDaoImpl serviceIUserServiceUserServiceImpl ApplicationContext 配置文件初始化 IOC 容器RunApplication 注解初始化 IOC 容器BeanAutowired Reference 项目结构 entity User Data NoArgsConstructor AllArgsConstructor Accessors(chai…

神经网络中使用的激活函数有什么用?

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; &#x1f50d;【大厂面试真题】系列&#xff0c;带你攻克大厂面试真题&#xff0c;秒变offer收割机&#xff01; ❓今日问题&am…

最新仿蓝奏网盘系统源码 附教程

自带的蓝奏云解析&#xff0c;是之前的代码&#xff0c;截至发帖时间&#xff0c;亲测依旧有效&#xff0c;可以扒拉下来做蓝奏云解析接口。 使用方法&#xff1a;可以将文件上传至蓝奏云&#xff0c;然后通过此套系统&#xff0c;二次解析下载&#xff0c;不会暴露你的真实蓝…

PCL 点云配准-4PCS算法(粗配准)

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 加载点云数据 2.1.2 执行4PCS粗配准 2.1.3 可视化源点云、目标点云和配准结果 2.2完整代码 三、实现效果 3.1原始点云 3.2配准后点云 PCL点云算法汇总及实战案例汇总的目录地址链接…

扫雷(C 语言)

目录 一、游戏设计分析二、各个步骤的代码实现1. 游戏菜单界面的实现2. 游戏初始化3. 开始扫雷 三、完整代码四、总结 一、游戏设计分析 本次设计的扫雷游戏是展示一个 9 * 9 的棋盘&#xff0c;然后输入坐标进行判断&#xff0c;若是雷&#xff0c;则游戏结束&#xff0c;否则…

FPGA实现PCIE采集电脑端视频转SFP光口UDP输出,基于XDMA+GTX架构,提供4套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案1G/2.5G Ethernet Subsystem实现物理层方案1G/2.5G Ethernet PCS/PMA or SGMII Tri Mode Ethernet MAC实现物理层方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图电脑端视频PCIE视频采集QT上位机X…

VSCODE c++不能自动补全的问题

最近安装了vscode&#xff0c;配置了C/C扩展&#xff0c;也按照网上说的配置了头文件路径 我发现有部分头文件是没办法解析的&#xff0c;只要包含这些头文件中的一个或者多个&#xff0c;就没有代码高亮和代码自动补全了&#xff0c;确定路径配置是没问题的&#xff0c;因为鼠…

【GT240X】【3】Wmware17和Centos 8 安装

文章目录 一、说明二、安装WMware2.1 下载WMware2.2 安装2.3 虚拟机的逻辑结构 三、安装Centos3.1 获取最新版本Centos3.2 创建虚拟机 四、问题和简答4.1 centos被淘汰了吗&#xff1f;4.2 centos里面中文显示成小方块的解决方法4.3 汉语-英语输入切换4.4 全屏和半屏切换 五、练…

【图论】(一)图论理论基础与岛屿问题

图论理论基础与岛屿问题 图论理论基础深度搜索&#xff08;dfs&#xff09;广度搜索&#xff08;bfs&#xff09;岛屿问题概述 岛屿数量岛屿数量-深搜版岛屿数量-广搜版 岛屿的最大面积孤岛的总面积沉没孤岛建造最大人工岛水流问题岛屿的周长 图论理论基础 这里仅对图论相关核…

精英高匿ip的自述

大家好&#xff0c;我是精英高匿IP。在网络世界里&#xff0c;我有着自己独特的看家本领&#xff0c;今天我就让大家见识一下我的本事。 我是一个神秘的网络侠客&#xff0c;我在数据的江湖中穿梭自如且不留痕迹。大家可能好奇我为什么叫精英高匿IP。“精英”代表着我拥有卓越…

【命令操作】Linux上通过mdadm配置软RAID _ 统信 _ 麒麟 _ 方德

往期好文&#xff1a;【功能介绍】麒麟2403支持配置任务栏上的图标“从不合并”啦&#xff01; Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于如何在Linux系统上使用mdadm工具配置软件RAID&#xff08;Redundant Array of Independent Disks&#xff0c;独立磁…

高频面试手撕

手撕高频结构 前言&#xff0c;以下内容&#xff0c;都是博主在秋招面试中&#xff0c;遇到的面试手撕代码题目&#xff0c;包含常见的数据结构、多线程以及数据库连接池等。 ArrayList 实现了ArrayList的基本功能&#xff0c;包括随机访问和自动扩容。 添加元素时&#xff…

施磊C++ | 进阶学习笔记 | 1.对象的应用优化、右值引用的优化

一.对象的应用优化、右值引用的优化 文章目录 一.对象的应用优化、右值引用的优化1.1 构造&#xff0c;拷贝&#xff0c;赋值&#xff0c;析构中的优化课后练习&#xff1a; 1.2 函数调用过程中对象背后调用的方法1.3 对象优化三原则1.4 右值引用、move移动语意、完美转发 1.1 …