成都如何做网站,网络公司网站建设费入什么科目,网站的建设步骤,大庆信息网前言#xff08;项目背景#xff09;#xff1a;
这个API签名认证是API开放平台得一个重要环节#xff0c;我们知道#xff0c;这个API开发平台#xff0c;用处就是给客户去调用现成得接口来完成某些事情得。 在讲API签名认证之前#xff0c;我们先模拟一个场景并且介绍…前言项目背景
这个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中HashMapString, 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){HashMapString, 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得依赖
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.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是asecretKey是abc传递的参数body是c然后可能还有时间戳或者随机数一起经过加密算法算出来的值是kjlkj
我后端有用户的accessKeysecretKey随机数然后我将这些变量全都拼起来然后用同样的加密算法进行计算算出来的值相同才算通过并且上面有说过重放问题我这样可以进行校验这个时间戳离我当前的时间如果超过了一定的时间我就也不算通过。
说了这么多我们来梳理一下我们前后端需要交接的参数
accessKey随机数这个可以存储在用户的列表中不过得定期删除时间戳用户的请求参数由加密算法算出来的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的用户QueryWrapperUser queryWrapper new QueryWrapper();queryWrapper.eq(accessKey,accessKey);User user userService.getOne(queryWrapper);System.out.println(user.getUserAccount());System.out.println();if(usernull){//usernull说明这个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 HashMapString,String addHeader(String body){HashMapString, 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。