API签名认证

前言(项目背景):

这个API签名认证是API开放平台得一个重要环节,我们知道,这个API开发平台,用处就是给客户去调用现成得接口来完成某些事情得。


在讲API签名认证之前,我们先模拟一个场景并且介绍一个java工具类

模拟场景:

我们新建一个模块,新建一个controller层来接受请求,再创建一个客户端来发送请求

所以我们得场景就是服务端和客户端的请求。

服务端controller层代码:
@RestController
@RequestMapping("/name")
public class nameController {@Resourceprivate UserService userService;@GetMapping("/get")public String GetNameByGet(String name){return "Get 你的名字是"+name;}@PostMapping("/")public String PostNameByPost(@RequestParam String name){return "Post 你的名字是"+name;}
}

非常简单就是两个请求接口,方式分别是Get和Post

客户端代码:
public class HttpHutool {public String GetNameByGet(String name){//可以单独传入http参数,这样参数会自动做URL编码,拼接在URL中HashMap<String, Object> paramMap = new HashMap<>();paramMap.put("name",name);String result= HttpUtil.get("http://localhost:8123/api/name/", paramMap);System.out.println(result);return result;}public String PostNameByPost(@RequestParam String name){HashMap<String, Object> paramMap = new HashMap<>();paramMap.put("name", "ljh");String result= HttpUtil.post("http://localhost:8123/api/name/", paramMap);System.out.println(result);return result;}}

这里介绍一个工具:Hutool

简介 | Hutool

Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug

这是Hutool得官方解释

把这个Hutool看成一个工具类即可

Hutool得依赖:
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency>
1798519188993286146_0.41094494896964641798519188993286146_0.2456341272020588

在客户端代码中,用Hutool来向Controller发送请求

测试类代码:
public class Main {public static void main(String[] args) {HttpHutool httpHutool = new HttpHutool();final String result1 = httpHutool.GetNameByGet("ljh1");final String result2 = httpHutool.PostNameByPost("ljh2");User user = new User();user.setName("ljh3");final String result3 = httpHutool.PostNameByPostRestful(user);System.out.println(result1);System.out.println(result2);System.out.println(result3);}
}

这样就可以简单模拟出结果了。

下面就要引出这篇文章得主角了:

API签名认证:

我们现在要思考一个重要问题:如果我们为开发者提供了一个接口,却对调用者一无所知。假设我们的服务器只能允许100个人同时调用接口。如果有攻击者疯狂地请求这个接口,那将极其危险。一方面这可能会损害我们的安全性,另一方面也可能耗尽服务器性能,影响正常用户的使用。
因此,我们必须为接口设置保护措施,例如限制每个用户每秒只能调用十次接口,即实施请求频次的限额控制。如果在后期,你的业务扩大,可能还需要收费。因此,我们必须知道谁在调用接口,并且不能让无权限的人随意调用。
现在,我们需要设计一个方法,来确定谁在调用接口。在我们之前开发后端时,我们会进行一些权限检查。例如,当管理员执行删除操作时,后端需要检查这个用户是否为管理员。那么,我们如何获取用户信息呢?是否直接从后端的 session 中获取?但问题来了,当我们调用接口时,我们有 session 吗?比如说,我是前端直接发起请求,我没有登录操作,我没有输入用户名和密码,我怎么去调用呢?因此,一般情况下,我们会采用一个叫API签名认证的机制。这是一个重要的概念。
那么,什么是API签名认证?简单地说,如果你想来我家做客,我不可能随便让任何陌生人进来。所以我会提前给你发一个类似于请帖的东西,作为授权或许可证。当你来访问我的时候,你需要带上这个许可证。我可能并不认识你,但我认识你的请帖。只要你有这个请帖,我就允许你进来。
所以,API签名认证主要包括两个过程。第一个是签发签名,第二个是使用签名或校验签名。这就像一些短信接口的key一样。为什么我们需要API签名认证呢?简单地说,第一,为了保证安全性,不能让任何人都能调用接口。那么,我们如何
在后端实现签名认证呢?我们需要两个东西,即 accessKey 和 secretKey。这和用户名和密码类似,不过每次调用接口都需要带上,实现无状态的请求(这里解释一下什么叫无状态请求,像我们平常上网,我们登录过之后,下次访问可能就会有记录,下一次就不需要再重新登录了,不过这个不一样,你每次来都要登录)这样,即使你之前没来过,只要这次的状态正确,你就可以调用接口。所以我们需要这两个东西来标识用户。
下面将为大家演示如何签发 accessKey 和 secretKey,以及如何使用和验证它们。在签发过程中,你可以自己编写一个生成 accessKey 和 secretKey 的工具。一般来说,accessKey 和 secretKey 需要尽可能复杂,以防止黑客尝试破解,特别是密码,需要尽可能复杂,无规律。

 知道了上面得流程之后,我们来先写一个简单得签名认证流程:

1:在数据库得user表中加入两个字段accessKey和secretKey

2:在用户端带上这两个凭证:

并且在测试类中修改一下

3:服务端接口校验:

我们需要获取用户传递的 accessKey 和 secretKey。对于这种数据,建议不要直接在URL 中传递,而是选择在请求头中传递会更为妥当。因为GET请求的URL 存在最大长度限制,如果你传递的其他参数过多,可能会导致关键数据被挤出。因此,建议从请求头中获取这些数据。

这里就用了一下别人得笔记,我自己写得时候得方法已经被添加了很多东西

这样我们简单得签名认证逻辑就写好了

在发送请求得时候,控制台就会输出信息,如果你得签名是对的,就通过。

问题:

不过我们看到这个简单的流程中有一个巨大的问题

就是:secretKey在服务器中发送。

 问题在于我们的请求有可能被人拦截,我们将密码放在请求头中,如果有中间人拦截到了你的请求,他们就可以直接从请求头中获取你的密码,然后使用你的密码发送请求。

比如你向后端发送请求,secretKey被拦截下来了,然后你这个时候肯定收不到服务端的回应,你可能就是觉得网络卡了,没当回事,其实是你的secretKey被拦截了,别人等过一段时间之后重新带着你的secretKey向后端发送请求,就可以获取到需要的资源了。

所以密码绝对不能传递。也就是说,在向对方发送请求时,密码绝对不能以明文的方式传递,必须通过特殊的方式进行传递。因此,我们目前的做法是行不通的。绝对不能直接在服务端或服务器之间传递密钥,这样是不安全的。那么,我们应该如何使其安全呢?在标准的API签名认证中,我们需要传递一个签名。通常我们不是直接将其传递给后台,而是根据该密钥生成一个签名。因为密码不能直接在服务器中传递,有可能会被拦截。

所以我们需要对该密码进行加密,这里通常称之为签名。那么这个签名是如何生成的呢?让我们思考一下,我们可以将用户传递的参数(例如ABC参数)与该密钥拼接在一起,然后使用签名算法进行加密。但这里实际上并不是真正的加密,也可以使用加密算法。

加密算法:

我们的加密算法可以分为:单向加密(md5签名)、对称加密、非对称加密。对称加密是什么?它是分配一组密钥,你 可以加密和解密。还有非对称加密,你可以使用公钥加密,私钥解密,有些情况下也可以使用私钥加密,公钥解密。此外,还有一种单向加密,即加密后无法解密。这种是安全性最高的
md5 本质上是一种签名算法。例如,百度网盘上传文件时,每个文件都有一个唯一的值。就像md5,一般情况下是 不可逆的,即无法解密。理论上,md5 这种方式最安全。所以我们的思路不是给用户分配的密钥来进行加密。而是我们将其与用户的参数进行拼接。例如,我们的密钥是 ABCDEFGH,经过签名算法后,最后得到的值可能是 fgthtrgerge。这个值是无法解密的。然后我们只需将此值发送给服务器,服务器只需验证签名是否正确即可。这样,我们根本就不传递密码,就是根据密码生成一个值传给服务器

然而问题来了,既然这个值无法解密,作为我的API接口,我如何知道你传递的签名是否正确呢?如何判断?这个
很简单,我可以再次使用相同的参数进行生成,并与你传递的参数进行对比,看它们是否一致。

就比如,你的accessKey是a,secretKey是abc,传递的参数body是:c,然后可能还有时间戳或者随机数一起经过加密算法算出来的值是kjlkj,

我后端有用户的accessKey,secretKey,随机数,然后我将这些变量全都拼起来然后用同样的加密算法进行计算,算出来的值相同才算通过,并且上面有说过重放问题,我这样可以进行校验这个时间戳离我当前的时间,如果超过了一定的时间,我就也不算通过。

说了这么多,我们来梳理一下,我们前后端需要交接的参数

  1. accessKey
  2. 随机数(这个可以存储在用户的列表中,不过得定期删除)
  3. 时间戳
  4. 用户的请求参数
  5. 由加密算法算出来的sign参数

 服务端controller代码:

@PostMapping("/restful")public String PostNameByPostRestful(@RequestBody User name, HttpServletRequest request){String accessKey = request.getHeader("accessKey");String body = request.getHeader("body");String random = request.getHeader("random");String timestamp = request.getHeader("timestamp");String sign = request.getHeader("sign");//查找数据库中被分配accessKey的用户QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("accessKey",accessKey);User user = userService.getOne(queryWrapper);System.out.println(user.getUserAccount());System.out.println("=================");if(user==null){//user==null,说明这个accessKey根本没有被分配给用户throw new RuntimeException("accessKey不存在");}if(Long.parseLong(random) > 10000){throw new RuntimeException("无权限");}//通过时间戳判断是否过期long currentTimeMillis = System.currentTimeMillis()/1000;long differenceInSeconds = (long) (currentTimeMillis/1000 - Long.parseLong(timestamp));if(differenceInSeconds > 300){throw new RuntimeException("时间戳超时");}//从数据库中查出secretKey//将secretKey和请求参数body再次进行计算算出flag和从请求头中得sign进行比较String secretKey = user.getSecretKey();final String flag = SignUtils.getSign(body, secretKey);if(!flag.equals(sign)){throw new RuntimeException("无权限");}return "PostRestful 你的名字是"+name;}

 这里整体的controller代码的逻辑是:

首先先从请求头中取出对呀的参数

再根据accessKey从数据库中查找或者根据request写一个获取当前用户的方法进行校验

接着再判断时间戳是否过期

然后取出用户的secretKey,进行加密计算算出flag与用户在请求头中传过来的sign进行对比

客户端代码:

public HashMap<String,String> addHeader(String body){HashMap<String, String> headermap = new HashMap<>();headermap.put("accessKey",accessKey);
//        headermap.put("secretKey",secretKey);headermap.put("body",body);//生成一个长度为4的随机数字headermap.put("random", RandomUtil.randomNumbers(4));headermap.put("timestamp",String.valueOf(System.currentTimeMillis()/1000));headermap.put("sign", SignUtils.getSign(body,secretKey));return headermap;}public String PostNameByPostRestful(@RequestBody Body name){String json = JSONUtil.toJsonStr(name);HttpResponse result = HttpRequest.post("http://localhost:8123/api/name/restful").addHeaders(addHeader(json)).body(json).execute();System.out.println(result.getStatus());System.out.println(result);return result.body();}

这里客户端代码分为两部分,一个是发送请求PostNameByPostRestful,另一个是拼接参数addHeader。 

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

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

相关文章

产业分析三部曲:如何快速完成存客产业识别、产业分布分析、区域产业分析?

2024年7月15日至18日&#xff0c;中国共产党第二十届中央委员会第三次全体会议在北京举行&#xff0c;审议通过了《中共中央关于进一步全面深化改革、推进中国式现代化的决定》。 《决定》提出&#xff0c;深化国资国企改革&#xff0c;完善管理监督体制机制&#xff0c;推动国…

Mistral新旗舰决战Llama 3.1,最强开源Large 2 123B,扛鼎多语言编程全能王

【新智元导读】紧跟着Meta的重磅发布&#xff0c;Mistral Large 2也带着权重一起上新了&#xff0c;而且参数量仅为Llama 3.1 405B的三分之一。不仅在编码、数学和多语言等专业领域可与SOTA模型直接竞争&#xff0c;还支持单节点部署。 昨天正式发布的Llama 3.1模型&#xff0…

react中路由跳转以及路由传参

一、路由跳转 1.安装插件 npm install react-router-dom 2.路由配置 路由配置&#xff1a;react中简单的配置路由-CSDN博客 3.实现代码 // src/page/index/index.js// 引入 import { Link, useNavigate } from "react-router-dom";function IndexPage() {const …

CSS常见属性详解——内边距与外边距

内边距与外边距 内边距 外边距 应用场景 在网页排版布局时&#xff0c;我们经常会希望元素与元素之间有一定的间距&#xff0c;此时我们可能会用到CSS的外边距或内边距属性&#xff0c;这两个属性都能让元素之间产生距离&#xff0c;那么他们之间有什么不同呢&#xff1f; …

Nginx系列-10 realIp模块使用

背景 Nginx对每个模块都有说明文档&#xff0c;可参考:https://nginx.org/en/docs/ 当请求被代理后&#xff0c;真实客户端相对服务器被隐藏&#xff0c;即服务端无法判断HTTP消息来源。 如上图所示&#xff0c;IP分别为100.100.100.1和100.100.100.2的两个客户端向服务器200.…

08 字符串和字节串

使用单引号、双引号、三单引号、三双引号作为定界符&#xff08;delimiter&#xff09;来表示字符串&#xff0c;并且不同的定界符之间可以相互嵌套。 很多内置函数和标准库对象也都支持对字符串的操作。 x hello world y Python is a great language z Tom said, "Le…

centos7 mysql 基本测试(6)主从简单测试

centos7 xtrabackup mysql 基本测试&#xff08;6&#xff09;主从简单测试 mysql -u etc -p 1234aA~1 参考&#xff1a; centos7 时区设置 时间同步 https://blog.csdn.net/wowocpp/article/details/135931129 Mysql数据库&#xff1a;主从复制与读写分离 https://blog.csd…

HTML常见标签——超链接a标签

一、a标签简介 二、a标签属性 href属性 target属性 三、a标签的作用 利用a标签进行页面跳转 利用a标签返回页面顶部以及跳转页面指定区域 利用a标签实现文件下载 一、a标签简介 <a>标签用于做跳转、导航&#xff0c;是双标签&#xff0c;记作<a></a>&#…

移动式气象站:科技赋能,精准预报的新篇章

在这个气候多变、极端天气频发的时代&#xff0c;气象信息的准确性与及时性成为了社会各界关注的焦点。从农业生产到城市规划&#xff0c;从航空航海到日常生活&#xff0c;气象服务无处不在&#xff0c;其重要性不言而喻。而在这场气象科技的变革中&#xff0c;移动式气象站以…

数据结构(Java):Map集合Set集合哈希表

目录 1、介绍 1.1 Map和Set 1.2 模型 2、Map集合 2.1 Map集合说明 2.2 Map.Entry<K&#xff0c;V> 2.3 Map常用方法 2.4 Map注意事项及实现类 3、Set集合 3.1 Set集合说明 3.2 Set常用方法 3.3 Set注意事项及其实现类 4、TreeMap&TreeSet 4.1 集合类TreeM…

「AI绘画Stable Diffusion 零基础入门 」AI 绘画SD原理与工具介绍,万字详解新手入门必看!

大家好&#xff0c;我是设计师阿威 AI 绘画原理 想要入门 AI 绘画&#xff0c;首先需要了解它的原理是什么样的。 其实很早就已经有人基于深度学习模型展开了对图像生成的研究了&#xff0c;但在那时&#xff0c;生成的图像分辨率和内容都非常抽象。 直到近两年&#xff0c…

【数据结构-前缀和】力扣3152.特殊数组II

如果数组的每一对相邻元素都是两个奇偶性不同的数字&#xff0c;则该数组被认为是一个 特殊数组 。 周洋哥有一个整数数组 nums 和一个二维整数矩阵 queries&#xff0c;对于 queries[i] [fromi, toi]&#xff0c;请你帮助周洋哥检查子数组 nums[fromi…toi] 是不是一个 特殊…

VLAN通讯实验

目录 拓扑图 需求 需求分析 配置过程 1、手工配置 2、 使用DHCP获得IP地址信息 3、测试全网是否可达 拓扑图 需求 1、PC1、PC3属于VLAN 2 2、PC2、PC4属于VLAN 3 3、通过DHCP使得PC获取IP地址信息 4、全网可达 需求分析 1、先手工配置网段&#xff0c;VLAN 2为192.168.1…

数据结构经典测试题4

1. #include <stdio.h> int main() { char *str[3] {"stra", "strb", "strc"}; char *p str[0]; int i 0; while(i < 3) { printf("%s ",p); i; } return 0; }上述代码运行结果是什么&#xff1f; A: stra strb strc B: s…

【用最少数量的箭引爆气球】python刷题记录

R2-贪心篇. 求最小&#xff0c;那就尽可能地假设更多的气球y值不相同咯。 不对&#xff0c;气球除了y值我们随便摆&#xff0c;所以找尽可能多重叠的&#xff0c;就作为同一只箭。 class Solution:def findMinArrowShots(self, points: List[List[int]]) -> int:#贪心策略…

原生PHP/JS自主开发的交友内核框架婚恋交友系统V10

本文来自&#xff1a;婚恋交友系统V10 - 源码1688 应用介绍 原生PHP/JS自主开发的交友内核框架&#xff0c;极高性能、无捆绑、自主权、无流水扣点、独立全开源 01脱单盲盒&#xff1a;脱单盲盒类似于漂流瓶&#xff0c;先将自己《投放》到盲盒中&#xff0c;另一伴有缘将您取…

【文件fd】深入理解和实现Linux底下一切皆文件 | 系统和语言文件操作二者关系_封装 | 系统调用为什么怎样封装成库函数

目录 1.系统调用的打开/读/写文件操作 2.如何理解Linux底下一切皆文件 2.1设备属性 2.2设备的操作方法 3.如何实现Linus底下一切皆文件 4.源码查看 5.系统和语言文件操作二者关系 5.1 flags选项和C语言的"w""a"方式 二者的关系 5.2 系统的文件描…

Linux之基础IO(下)

目录 缓冲区的概念 深入理解文件系统 创建文件的整个过程 软链接 硬链接 上一节课我们学习了基础IO中的文件的读写操作&#xff0c;以及文件描述符的概念和重定向的基本原理&#xff0c;本期我们继续进行基础IO的学习。 缓冲区的概念 在讲缓冲区之前&#xff0c;大家先看…

Java 集合框架:HashMap 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 020 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

Redis (常用数据结构和命令)

目录 简介 概述 特点 数据结构 常用命令 通用命令 keys del exists expire 与 ttl String 命令 SET 和GET: MSET和MGET INCR和INCRBY和DECY SETNX SETEX Redis 命令 Key 的层级结构 key层级关系 &#xff1a; Hash命令 HSET和HGET HMSET和HMGET HGETALL H…