如何在项目中应用“API签名认证”

❤ 作者主页:李奕赫揍小邰的博客
❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*
🍊 记得点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习!!!🎉🎉

文章目录

  • 为什么需要API签字认证?
  • API签名认证需要的参数
  • API签名认证实现
    • 1.用户注册创建ak,sk
    • 2.加密算法
    • 3.发送调用接口请求
    • 4.验证签名是否正确
  • 总结如何改善开发:

 
  最近在做一个API开放平台,因为开发者可以上传接口到这个平台上面,然后用户可以浏览平台进行付费次数调用。但值得思考的是,如果用户量很大,或者攻击者疯狂请求这个接口,这会导致安全问题,同时也会耗尽服务器性能,影响正常用户的使用。
  因此需要进行保护措施,所以我使用的API签名认证进行权限认证。

为什么需要API签字认证?

  为了保证安全性,不能让任何人都能调用接口。那么,我们如何在后端实现签名认证呢?我们需要两个东西,即 accessKey 和 secretKey。这和用户名和密码类似,不过每次调用接口都需要带上,实现无状态的请求。这样,即使你之前没来过,只要这次的状态正确,你就可以调用接口。所以我们需要这两个东西来标识用户。
  他的本质就是签发签名,使用签名。同时需要运用到加密算法,对ak,sk加密成签名。

 

API签名认证需要的参数

参数 1:accessKey:调用的标识 (复杂、无序、无规律)
参数 2:secretKey:密钥(复杂、无序、无规律)
参数 3:用户请求参数
参数 4:sign(加密的算法)
参数 5:加 nonce 随机数,只能用一次。服务端要保存用过的随机数,防止重放请求
参数 6:加 timestamp 时间戳,校验时间戳是否过期。防止重放请求。
 
防止重放作用
  每个请求在发送时携带一个时间戳,后端会验证该时间戳是否在指定的时间范围内,例如不超过10分钟或5分钟。这可以防止对方使用昨天的请求在今天进行重放。通过这种方式,我们可以一定程度上控制随机数的过期时间。因为后端需要同时验证这两个参数,只要时间戳过期或随机数被使用过,后端会拒绝该请求。因此,时间戳可以在一定程度上减轻后端保存随机数的负担。通常情况下,这两种方法可以相互配合使用。

 

API签名认证实现

 

1.用户注册创建ak,sk

  1.每个用户在注册的时候,就需要创建ak,sk

/**
* 盐值,混淆密码
*/
private static final String SALT = "123";
public long userRegister(String userAccount, String userPassword, String checkPassword) {// 1. 校验if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");}if (userAccount.length() < 4) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");}if (userPassword.length() < 8 || checkPassword.length() < 8) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");}// 密码和校验密码相同if (!userPassword.equals(checkPassword)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");}synchronized (userAccount.intern()) {// 账户不能重复QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userAccount", userAccount);long count = userMapper.selectCount(queryWrapper);if (count > 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");}// 2. 加密String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());//3.分配accessKey,secretKey//使用DigestUtil.md5Hex将盐值、用户账户和5或8位随机数进行MD5加密String accessKey = DigestUtil.md5Hex(SALT+userAccount+ RandomUtil.randomNumbers(5));String secretKey = DigestUtil.md5Hex(SALT+userAccount+ RandomUtil.randomNumbers(8));// 4. 插入数据User user = new User();user.setUserAccount(userAccount);user.setUserPassword(encryptPassword);user.setAccessKey(accessKey);user.setSecretKey(secretKey);boolean saveResult = this.save(user);if (!saveResult) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");}return user.getId();}}

在我们注册之中,首先使用MD5对密码进行盐值加密。之后就是生成用户专属的ak,sk。利用DigestUtil加密算法,将盐值,用户账号,随机数进行MD5加密生成,这样就能生成用户专属的签名。
 

2.加密算法

  加密算法种类相当多。对称加密、非对称加密、md5 签名(不可解密)等等。我们在这里使用的是SHA256算法的Digester。当然使用其他的算法都是可以的,因为到时候验证签名的时候,只需要再加密一遍,得到的ak1,sk1和库里面加密后的ak,sk一样,即代表着验证成功,

public class SignUtils {/*** 生成签名* @param body* @param secretKey 密钥* @return 生成的签名字符串*/public static String genSign(String body, String secretKey) {// 使用SHA256算法的DigesterDigester md5 = new Digester(DigestAlgorithm.SHA256);// 构建签名内容,将哈希映射转换为字符串并拼接密钥String content = body + "." + secretKey;// 计算签名的摘要并返回摘要的十六进制表示形式return md5.digestHex(content);}
}

将加密算法写成一个公共类,用的时候直接调用即可。

 

3.发送调用接口请求

当用户调用接口时,就应该将ak,sk,以及timestamp时间戳加到请求头之中。使用加密算法生成签名。最后发送请求。

// 获取当前登录用户的ak和sk,这样相当于用户自己的这个身份去调用,
// 也不会担心它刷接口,因为知道是谁刷了这个接口,会比较安全
User loginUser = userService.getLoginUser(request);
String accessKey = loginUser.getAccessKey();
String secretKey = loginUser.getSecretKey();
JjlApiClient jjlApiClient = new JjlApiClient(accessKey, secretKey);
/*** 通过请求方法获取http响应* @param request 要求* @return {@link HttpResponse}* @throws ApiException 业务异常*/
private <O, T extends ResultResponse> HttpRequest getHttpRequestByRequestMethod(BaseRequest<O, T> request) throws ApiException {if (ObjectUtils.isEmpty(request)) {throw new ApiException(ErrorCode.OPERATION_ERROR, "请求参数错误");}String path = request.getPath().trim();String method = request.getMethod().trim().toUpperCase();if (ObjectUtils.isEmpty(method)) {throw new ApiException(ErrorCode.OPERATION_ERROR, "请求方法不存在");}if (StringUtils.isBlank(path)) {throw new ApiException(ErrorCode.OPERATION_ERROR, "请求路径不存在");}log.info("请求方法:{},请求路径:{},请求参数:{}", method, path, request.getRequestParams());HttpRequest httpRequest;switch (method) {case "GET": {httpRequest = HttpRequest.get(path);break;}case "POST": {httpRequest = HttpRequest.post(path);break;}default: {throw new ApiException(ErrorCode.OPERATION_ERROR, "不支持该请求");}}//添加请求头,发送请求,return httpRequest.addHeaders(getHeaders(JSONUtil.toJsonStr(request), jjlApiClient)).body(JSONUtil.toJsonStr(request.getRequestParams()));}
//添加请求头
private Map<String, String> getHeaders(String body, JjlApiClient jjlApiClient) {Map<String, String> hashMap = new HashMap<>(4);hashMap.put("accessKey", jjlApiClient.getAccessKey());String encodedBody = SecureUtil.md5(body);hashMap.put("body", encodedBody);hashMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));hashMap.put("sign", SignUtils.genSign(encodedBody, jjlApiClient.getSecretKey()));return hashMap;}

 

4.验证签名是否正确

  因为请求接口的链接不同,做验证比较麻烦的话,我们可以写一个网关,将所有请求进行拦截。统一验证之后,再调用接口。这个之后篇章在实现。本文主要是介绍API签字认证功能。

// 首先从请求头中获取参数
HttpHeaders headers = request.getHeaders();
String accessKey = headers.getFirst("accessKey");
String nonce = headers.getFirst("nonce");
String timestamp = headers.getFirst("timestamp");
String sign = headers.getFirst("sign");
String body = headers.getFirst("body");
/获取当前用户
User invokeUser = null;
try{invokeUser = innerUserService.getInvokeUser(accessKey);
}catch (Exception e){log.error("getInvokeUser error",e);
}
if(invokeUser == null){return handleNoAuth(response);
}
// 直接校验如果随机数大于1万,则抛出异常,并提示"无权限"
if (Long.parseLong(nonce) > 10000) {return handleNoAuth(response);
}
// 时间和当前时间不能超过5分钟
// 首先,获取当前时间的时间戳,以秒为单位
Long currentTime = System.currentTimeMillis() / 1000;
final Long FIVE_MINUTES = 60 * 5L;
if ((currentTime - Long.parseLong(timestamp)) >= FIVE_MINUTES) {return handleNoAuth(response);
}
//从库中查询出sk,然后利用加密算法得出签名,两者一直则代表验证完成
String secretKey = invokeUser.getSecretKey();
String serverSign = SignUtils.genSign(body,secretKey);
if (sign == null || !sign.equals(serverSign)) {return handleNoAuth(response);
}

验证完之后就可以调用接口。返回接口值。
 


总结如何改善开发:

  作为开发者,每次调用接口都需要处理这一堆繁琐的事情,这确实有些麻烦,需要自己生成时间戳,编写签名算法,生成随机数等等,这些都是相当繁琐的工作。因此,构建接口开放平台时,需要想办法让开发者能够以最简单的方式调用接口。开发者只需要关心传递哪些参数以及他们的密钥、APP等信息。一旦告诉了他们这些信息,他们就可以轻松地进行调用了。
  对于具体的随机数生成和签名生成过程,开发者有必要关心吗?显然是不需要的。因此,我们需要为开发者提供一个易于使用的 SDK,使其能够便捷地调用接口。因此接下来会发布文章来教大家怎么开发SDK

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

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

相关文章

windows脚本启动程序的方式

1. 获取管理员权限并运行程序 powershell -command "Start-Process -Verb RunAs -FilePath xxx.exe -ArgumentList parameters......" 2. 无须管理员权限 powershell -command "Start-Process -FilePath xxx.exe -ArgumentList parameters......"

ORBSLAM3与GPS进行松组合

文章目录 预备知识orbslam3vins-fusion中的GPS融合策略数据准备环境准备更改配置globalOptNode.cpp代码运行融合结果预备知识 orbslam3 ORBSLAM3 是一个最新的视觉SLAM(Simultaneous Localization and Mapping)系统,它建立在成功的 ORBSLAM2 系统的基础上,引入了多种新的…

在SwiftUI中使用Buider模式创建复杂组件

在SwiftUI中使用Buider模式创建复杂组件 我们在前面的博客闲聊SwiftUI中的自定义组件中聊到了如何在SwiftU中创建自定义组件。 在那里&#xff0c;我们创建了一个非常简单的组件RedBox&#xff0c;它将展示内容增加一个红色的边框。 RedBox非常简单&#xff0c;我们用普通的方…

Java面试题总结12MySQL之MVCC和主从复制原理

什么是MVCC 即多版本并发控制&#xff0c;读取数据时通过一种类似快照的方式将数据保存下来&#xff0c;这样读写与写锁就不会 冲突&#xff0c;不同的事务session只会看到自己特定版本的数据&#xff0c;版本链 MVCC只会在READ COMMITTED(已提交读)和REPEATABLE READ(可重复…

腾讯春招后端一面(算法篇)

前言&#xff1a; 哈喽大家好&#xff0c;前段时间在小红书和牛客上发了面试的经验贴&#xff0c;很多同学留言问算法的具体解法&#xff0c;今天就详细写个帖子回复大家。 因为csdn是写的比较详细&#xff0c;所以更新比较慢&#xff0c;大家见谅~~ 就题目而言&#xff0c;…

深度学习_GoogLeNet_4

目标 知道GoogLeNet网络结构的特点能够利用GoogLeNet完成图像分类 一、开发背景 GoogLeNet在2014年由Google团队提出&#xff0c; 斩获当年ImageNet(ILSVRC14)竞赛中Classification Task (分类任务) 第一名&#xff0c;VGG获得了第二名&#xff0c;为了向“LeNet”致敬&#x…

Java应用卡死 生产故障深度分析与排查实战

一、引言 在实际的生产环境中&#xff0c;我们经常会遇到Java应用出现线程阻塞进而导致服务卡死的问题。这种问题不仅影响用户体验&#xff0c;严重时甚至会导致整个系统崩溃。本文将通过一次真实的生产故障案例&#xff0c;详解从发现异常到定位原因的详细步骤&#xff0c;并…

Linux学习之网络

目录 认识协议 网络协议初始 协议分层 OSI七层模型 TCP/IP的四层模型 数据包封装和分用 以太网通信 ip地址与MAC地址 网络编程套接字 端口号&#xff08;port&#xff09; 认识协议 网络字节序 socket接口 网络的产生是计算机历史的必然性&#xff0c;是计算机发展…

SwiftUI组件-DatePicker

SwiftUI组件-DatePicker 本文记录一下SwiftUI组件-DatePicker import SwiftUIstruct DatePickerBootCamp: View {State var selectedDate: Date Date()var dateFormatter: DateFormatter {let formatter DateFormatter()formatter.dateStyle .shortformatter.timeStyle .…

AI-逻辑回归模型

&#x1f606;&#x1f606;&#x1f606;感谢大家的支持~&#x1f606;&#x1f606;&#x1f606; 逻辑回归的应用场景 逻辑回归&#xff08;Logistic Regression&#xff09;是机器学习中的 一种分类模型 &#xff0c;逻辑回归是一种分类算法&#xff0c;虽然名字中带有回…

09-设计模式 企业场景 面试题

目录 1.简单工厂模式 ​编辑 2.工厂方法模式 3.抽象工厂模式 4.策略模式 5.登录案例(工厂模式+策略模式) 6.责任链设计模式 7.单点登录怎么是实现的? 8.权限认证是如何实现的 9.上传数据的安全性你们怎么控制? 10.你负责项目的时候遇到了哪些比较棘手的问题?怎…

《天软行业全景画像》报告第7期

内容概要&#xff1a; 传媒、环保、美容护理行业的拥挤度较高&#xff0c;实际投资交易应注意&#xff1b;煤炭行业动量较强&#xff0c;医药生物、综合、环保行业动量较弱&#xff0c;业绩整体表现较差&#xff1b;食品饮料、公用事业行业景气度较高&#xff0c;财务基本面状况…

MySQL的索引下推

一、什么事索引下推 MySQL的索引下推&#xff08;Index Condition Pushdown, ICP&#xff09;是一种优化查询的方式&#xff0c;它可以改善查询性能&#xff0c;特别是对于包含多个条件的查询。在没有索引下推的情况下&#xff0c;MySQL会先从索引中找到满足某一条件的行的指针…

unity-unity2d tilemap的基本使用笔记0.5.4000

unity2d tilemap的基本使用笔记 tilemap涉及到元素tile资源的处理步骤如何创建palette并使用如何科学让场景的物体都显示palette视图中的edit按钮在tilemap的实际操作技巧(如何实现层叠)如何提高Tilemap Collider 2D的性能如何调整tilemap的颜色如何调整一个tile的颜色关于Co…

【黑马程序员】Python文件、异常、模块、包

文章目录 文件操作文件编码什么是编码为什么要使用编码 文件的读取openmodel常用的三种基础访问模式读操作相关方法 文件的写入注意代码示例 异常定义异常捕获捕获指定异常捕获多个异常捕获所有异常异常else异常finally 异常的传递 python 模块定义模块的导入import模块名from …

Chroma向量数据库报错Delete of nonexisting embedding ID: 123-sql

Vanna使用ChromaDB_VectorStore时&#xff0c;删除训练数据&#xff0c;指定错误的id后&#xff0c;导致的错误Delete of nonexisting embedding ID: 123-sql 使用delete函数后&#xff0c;经常会出现如下错误&#xff0c;暂未修复&#xff0c;但是不妨碍使用&#xff1a; De…

基于Python的pygame库的五子棋游戏

安装pygame pip install pygame五子棋游戏代码 """五子棋之人机对战"""import sys import random import pygame from pygame.locals import * import pygame.gfxdraw from collections import namedtupleChessman namedtuple(Chessman, Name…

python 基础知识点(蓝桥杯python科目个人复习计划63)

今日复习内容&#xff1a;做题 例题1&#xff1a;蓝桥骑士 问题描述&#xff1a; 小蓝是蓝桥王国的骑士&#xff0c;他喜欢不断突破自我。 这天蓝桥国王给他安排了N个对手&#xff0c;他们的战力值分别为a1,a2,...,an&#xff0c;且按顺序阻挡在小蓝的前方。对于这些对手小…

【LeetCode】动态规划--题目练习

有关动态规划算法的整理&#xff1a;添加链接描述 1.爬楼梯 爬楼梯:LeetCode70 int climbStairs(int n) {//1.确定dp数组和意义 dp[n]表示第n阶的方法//2.确定递推关系式 dp[n] dp[n-1]dp[n-2];//3.初始化int dp[50] {0};dp[1] 1;dp[2] 2;for(int i 3;i<n;i){dp[i] …

C++核心高级编程

文章目录 C++核心高级编程1.内存分区模型1.1 程序运行前1.2 程序运行后1.3 new操作符2.引用2.1 使用2.2 注意事项2.3 做函数参数2.4 做函数返回值2.5 本质2.6 常量引用3.函数提高3.1 函数默认参数3.2 函数占位参数3.3 函数重载3.3.1 函数重载概述3.3.2 注意事项4.类和对象4.1 封…