开放API签名认证的设计并开发对应的SDK

1. 为什么需要签名认证呢?

假设我们开放了一个接口,而我们的服务器只允许处理1000个请求,如果这个时候由用户疯狂发送几万个请求,可能会导致服务器宕机,影响其他用户的正常使用。这个情况下我们需要对接口进行限流,而如果我们接口的内容很重要,有一定的保密性 ,这个时候就不可以随便让用户调用,需要让用户去申请签名认证来调取接口,通过接口的认证之后才可以访问到资源。

2. 代码设计

我们以一个简单的接口为例,一步一步从接口的开发到签名认证系统的设计:

2.1. 接口开发

首先我们先简单的创建get和post请求的接口:

package com.stukk.model;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @Author: stukk* @Description: 用户类(仅供测试)* @DateTime: 2023-12-20 16:20**/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {private String userName;
}package com.stukk.controller;import com.stukk.model.User;
import org.springframework.web.bind.annotation.*;/*** @Author: stukk* @Description: 测试开发API的接口* @DateTime: 2023-12-20 16:20**/
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/")public String getNameByGet(String name){return "用户名是:"+name;}@PostMapping("/name")public String getNameByPost(@RequestParam String name){return "用户名是:"+name;}@PostMapping("/")public String getUserNameByPost(@RequestBody User user){return "用户名是:"+user.getUserName();}}

application.yml文件配置:

server:port: 8099servlet:context-path: /api

启动springboot之后,我们在浏览器输入:localhost:8099/api/user/?name=stukk

2.2. 第三方接口的客户端开发

每次让用户使用http去调用显然不太好,我们需要封装方法给用户传参调用就行了。

package com.stukk.client;import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.stukk.model.User;
import org.springframework.web.bind.annotation.RequestParam;import java.util.HashMap;
import java.util.Map;/*** @Author: stukk* @Description: 第三方接口客户端* @DateTime: 2023-12-20 16:33**/
public class ApiClient {public String getNameByGet(String name){
//        Get请求Map<String,Object> paramMap = new HashMap<>();paramMap.put("name",name);String result = HttpUtil.get("http://localhost:8099/api/user/", paramMap);System.out.println(result);return result;}public String getNameByPost( String name){
//        Post请求Map<String, Object> paramMap = new HashMap<>();paramMap.put("name",name);String result = HttpUtil.post("http://localhost:8099/api/user/name", paramMap);System.out.println(result);return result;}public String getUserNameByPost(User user){
//        Post请求String jsonStr = JSONUtil.toJsonStr(user);Map<String, Object> paramMap = new HashMap<>();HttpResponse httpResponse = HttpRequest.post("http://localhost:8099/api/user/").body(jsonStr).execute();System.out.println(httpResponse.getStatus());String result = httpResponse.body();System.out.println(result);return result;}public static void main(String[] args) {ApiClient apiClient = new ApiClient();apiClient.getNameByGet("stukk");apiClient.getNameByPost("吴坤坤");apiClient.getUserNameByPost(User.builder().userName("kkkkkk").build());}}

运行结果:

好了,我们已经实现了接口的开发和调用了,但是上述例子只是一个简单的例子,具体情况还需要根据项目需求去添加内容。

2.3.简单的校验

上述接口最大的问题就是,任何人、任何时间都可以来访问我的接口,这样子相当于接口在裸奔了,安全性是没有的,所以我们可以初步一些简单的校验功能:

加上公钥和私钥:

    private String accessKey; //公钥private String secretKey; //私钥public ApiClient(String accessKey, String secretKey) {this.accessKey = accessKey;this.secretKey = secretKey;}

然后我们现在接口处加上很呆的判定:

    @PostMapping("/")public String getUserNameByPost(@RequestBody User user, HttpServletRequest request){String accessKey = request.getHeader("accessKey");String secretKey = request.getHeader("secretKey");if(!accessKey.equals("stukk") || !secretKey.equals("kkkkkk")){throw new RuntimeException("无权限");}return "用户名是:"+user.getUserName();}

之后运行就无权限了:

但是显然这种虽然能有效的防止别人随意调用,但是只要有人拦截了我们的请求,获取到请求头中的ak和sk,那么就能随意调用了,所以密码是不能以明文的形式传输的!不传递怎么确定是合规的访问呢?

2.4.标准的API签名认证

在标准的 API 签名认证中,我们需要传递一个签名(Sign)。通常我们不是直接将密钥传递给后台,而是根据密钥生成一个签名。我们可以使用MD5单向加密算法来加密密钥生成签名,单向意味着只可加密不可解密的,所以我们需要保存用户的密钥,在判定时,再加密一次对比加签名既可以判断是不是合规的请求了。

为了更加的安全,我们还可以

1. 在请求头加上随机数,后端只接受随机数一次,这样可以解决请求重放问题,更加的安全了

2. 每个请求在发送时携带一个时间戳,并且后端会验证该时间戳是否在指定的时间范围内,例如不超过10分钟或5分钟。这可以防止对方使用昨天的请求在今天进行重放。

详情见代码:

/*** @Author: stukk* @Description: 签名工具* @DateTime: 2023-12-20 17:47**/
public class SignUtil {public static String genSign(String body,String secretKey){
//        使用基于SHA256的MD5算法Digester md5 = new Digester(DigestAlgorithm.SHA256);String content = body + "." + secretKey;
//        加密得到签名return md5.digestHex(content);}
}/*** @Author: stukk* @Description: 第三方接口客户端* @DateTime: 2023-12-20 16:33**/
public class ApiClient {private String accessKey; //公钥private String secretKey; //私钥public ApiClient(String accessKey, String secretKey) {this.accessKey = accessKey;this.secretKey = secretKey;}public String getNameByGet(String name){
//        Get请求Map<String,Object> paramMap = new HashMap<>();paramMap.put("name",name);String result = HttpUtil.get("http://localhost:8099/api/user/", paramMap);System.out.println(result);return result;}public String getNameByPost( String name){
//        Post请求Map<String, Object> paramMap = new HashMap<>();paramMap.put("name",name);String result = HttpUtil.post("http://localhost:8099/api/user/name", paramMap);System.out.println(result);return result;}public String getUserNameByPost(User user){
//        Post请求String jsonStr = JSONUtil.toJsonStr(user);HttpResponse httpResponse = HttpRequest.post("http://localhost:8099/api/user/").addHeaders(getHeaders(jsonStr)).body(jsonStr).execute();System.out.println(httpResponse.getStatus());String result = httpResponse.body();System.out.println(result);return result;}private Map<String,String> getHeaders(String body){Map<String,String> map = new HashMap<>();map.put("accessKey",accessKey);
//        不能直接加上密钥map.put("secretKey",secretKey);
//        生成随机数,4个随机数字字符串map.put("nonce", RandomUtil.randomNumbers(4));
//        请求体map.put("body",body);
//        时间戳map.put("timestamp",String.valueOf(System.currentTimeMillis()/1000));
//        签名:map.put("sign", SignUtil.genSign(body,secretKey));return map;}
}package com.stukk.controller;import com.stukk.model.User;
import com.stukk.utils.SignUtil;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;/*** @Author: stukk* @Description: 测试开发API的接口* @DateTime: 2023-12-20 16:20**/
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/")public String getNameByGet(String name){return "用户名是:"+name;}@PostMapping("/name")public String getNameByPost(@RequestParam String name){return "用户名是:"+name;}@PostMapping("/")public String getUserNameByPost(@RequestBody User user, HttpServletRequest request){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");Set<String> nonces = new HashSet<>();
//        数据库查询验证这个accessKeyif(!accessKey.equals("stukk")){throw new RuntimeException("无权限");}
//        检验随机数,判断是不是出现过if(nonces.contains(nonce)){throw new RuntimeException("请重试");}nonces.add(nonce);long preTimestamp = Long.parseLong(timestamp);long nowTimestamp = System.currentTimeMillis()/1000;if(nowTimestamp - preTimestamp > 36000){ //10小时?throw new RuntimeException("签名超时");}//根据accessKey从数据库查出secretkey,这里假设为kkkkkkString secretLKey = "kkkkkk";String correctSign = SignUtil.genSign(body, secretLKey);if(!correctSign.equals(sign)){throw new RuntimeException("签名错误");}return "用户名是:"+user.getUserName();}
}/*** @Author: stukk* @Description:* @DateTime: 2023-12-20 17:09**/
public class UserClient {public static void main(String[] args) {String accessKey = "stukk"; //公钥String secretKey = "kkkkkk"; //私钥ApiClient apiClient = new ApiClient(accessKey, secretKey);apiClient.getUserNameByPost(User.builder().userName("kkkkkk").build());}}

这样子我们就完成了标准的API签名认证了,接下来开发SDK:

3.开发SDK

3.1.新建项目

3.2.配置pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.7.13</version><relativePath/></parent><groupId>com.stukk</groupId><artifactId>api-client-sdk</artifactId>
<!--    版本号--><version>1.1.1</version><name>api-client-sdk</name><description>api-client-sdk</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies></project>

3.3.创建配置类
package com.stukk;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;/*** @Author: stukk* @Description: 创建配置类* @DateTime: 2023-12-20 19:04**/
//标记为配置类
@Configuration
@ConfigurationProperties("stukk.api") //能够读取application.yml配置,载入属性
@Data //lombok注解
@ComponentScan //自动扫描组件
public class ApiClientConfig {private String accessKey;private String secretKey;
}

项目中的 client包、model包、utils包复制:

在resources目录下创建META-INF/spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.stukk.ApiClientConfig
3.4.下载jar包 

下载的地方就是我们maven配置的地方:

4.引入SDK 

接下来回到原来的项目,删掉model、client、utils包,引入这个SDK依赖:

        <dependency><groupId>com.stukk</groupId><artifactId>api-client-sdk</artifactId><version>1.1.1</version></dependency>

我们会发现application.yml会提示我们生成这个accessKey和secretKey配置:

server:port: 8099servlet:context-path: /api
stukk:api:access-key: stukksecret-key: kkkkkk

3.5编写测试类测试:

package com.stukk;import com.stukk.client.ApiClient;
import com.stukk.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest
class OpenApiApplicationTests {@Resourceprivate ApiClient apiClient;@Testvoid testSDK() {User user = new User(kk__SDK");String result = apiClient.getUserNameByPost(user);System.out.println(result);}}

成功调用了。 

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

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

相关文章

那些令人惊叹的awk简略写法

​​​​​​​awk是一门美妙的语言&#xff0c;被称为unix命令行工具皇冠上的明珠。它有很多简略写法&#xff0c;用好了可以用极少的代码快速解决问题。 下面就列举一些令人惊叹的awk简略写法&#xff1a; awk {sub(/pattern/, "foobar")} 1 # 无论替换是否成功&…

在scrapy 使用selenium模拟登录获取cookie

前言 最近有一点点爬虫需求&#xff0c;想总结一下scrapy框架的一些基本使用方法&#xff0c;加深印象&#xff0c;自己一直习惯使用一些脚本文件运行爬虫&#xff0c;面对数据量非常大&#xff0c;稳定性要求比较高的&#xff0c;效率需求比较高的情况下还是用scrapy较为合适…

Seata:打造行业首个分布式事务产品

作者&#xff1a;季敏&#xff0c;阿里云分布式事务产品负责人、Seata 开源项目创始人 微服务架构下数据一致性的挑战 微服务开发的痛点 在 2019 年&#xff0c;我们基于 Dubbo Ecosystem Meetup&#xff0c;收集了 2000 多份关于“在微服务架构&#xff0c;哪些核心问题是开…

【C++】开源:ImGui图形用户界面库配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍ImGui图形用户界面库配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

13 v-show指令

概述 v-show用于实现组件的显示和隐藏&#xff0c;和v-if单独使用的时候有点类似。不同的是&#xff0c;v-if会直接移除dom元素&#xff0c;而v-show只是让dom元素隐藏&#xff0c;而不会移除。 在实际开发中&#xff0c;v-show也经常被用到&#xff0c;需要重点掌握。 基本…

TypeScript【泛型1、泛型2、声明合并、命名空间 、模块1、模块2、声明文件简介】(五)-全面详解(学习总结---从入门到深化)

文章目录 泛型1 泛型2 声明合并 命名空间 模块1 模块2 声明文件简介 泛型1 泛型&#xff08;Generics&#xff09;是指在定义函数、接口或类的时候&#xff0c;不预先指定具体的类型&#xff0c;而在使用的时候再指定类型的一种特性 首先&#xff0c;我们来实现一个函数…

nuxt学习笔记

主要看的课程1 课程1 课程2 上手简化版 初始化 1.创建项目 使用官方推荐的npx来安装&#xff1a; (npm的5.2.x版本后默认安装了npx) 首先&#xff0c;确保您已经安装了 yarn、npx&#xff08;默认包含在 npm v5.2 中&#xff09;或 npm (v6.1)。 使用 npx 进行搭建项目&…

stm32学习总结:4、Proteus8+STM32CubeMX+MDK仿真串口收发

stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发 文章目录 stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发一、前言二、资料收集三、STM32CubeMX配置串口1、配置开启USART12、设置usart中断优先级3、配置外设独立生成.c和.h 四、MDK串口收发…

Ubuntu 常用命令之 unzip 命令用法介绍

unzip命令在Ubuntu系统中用于解压缩.zip文件。它可以解压缩一个或多个.zip文件&#xff0c;并将文件解压缩到当前目录或指定的目录。 unzip命令的一般格式 unzip [选项] zipfile [file...]其中&#xff0c;zipfile是要解压的.zip文件&#xff0c;file是.zip文件中的特定文件。…

关键字:void关键字

在编程中&#xff0c;void 是一个关键字&#xff0c;用于表示函数没有返回值。具体来说&#xff0c;void 关键字的作用如下&#xff1a; 函数声明&#xff1a;在函数声明中使用 void 关键字可以指定函数没有返回值。例如&#xff1a; 这表示 func() 函数不返回任何值。 函数…

flutter自定义地图Marker完美展示图片

世人都说雪景美 寒风冻脚无人疼 只道是一身正气 结论 参考Flutter集成高德地图并添加自定义Maker先实现自定义Marker。如果自定义Marker中用到了图片&#xff0c;那么会碰到图片没有被绘制到Marker的问题&#xff0c;此时需要通过precacheImage来预加载图片&#xff0c;从而解…