设计接口时,为其添加签名鉴权---详细教程
一、何为签名
我们知道无论是restful api还是传统接口、亦或是其他形式接口的调用,接口签名都是非常重要的安全机制,它可以确保请求的发起者是经过认证和授权的客户端,同时也可以防止接口被攻击,请求参数被篡改等等。
用大白话来解释就是,如果你写的接口没有签名验证,那么不管是谁都可以调用,只要路径、参数是对的就可以调用,这样就会非常不安全,因此我们会对接口添加签名验证。
首先,我们要了解原理,举个例子来讲,假设有一场线上的会议,而会议室呢并不是谁都可以进去的,光有会议室号是不行的还需要密码,如果更严格一些,可能还需要填写公共的信息内容等,那这些密码信息等,都是实现约定好的,提供给相关人就可以。
同理,签名也是双方约定好的,客户端(调用接口的用户)需要带着事先约定好的签名去调用,然后服务端(被调用)需要对这个签名进行验证,如果是他约定好的,那么就放行,让其直接调用即可。
二、签名设计
那么签名该如何设计呢?那就要说道签名的规则了。
签名一般会包括以下几部分:
- appid 和 app\secret
- timestamp 时间戳
- version 版本号
- signature 所有数据的签名信息。
接下来依次介绍他们的作用:
1) appid & appsecret
appid :应用的标识
appsecret:私匙(相当于密码)
通常我们会线下分配appid和appsecret,也就是提前声明好,针对不同的调用方分配不同的appid和appsecret。一般来说appid 和appsecret是一一对应的,用于对接口数据进行加密、解密。
2) timestamp(时间戳)用时间戳可以防止暴力请求
sign机制可以防止参数被篡改,但无法防ddos攻击(第三方使用正确的参数,不停的请求服务器,使之无法正常提供服务)。因此,还需要引入时间戳机制。
而服务端则需要根据当前时间和sign值的时间戳进行比较,差值超过一段时间则不予通过客户端的请求,直接给客户端响应某些错误提示等。
若要求不高,则客户端和服务端可以仅仅使用精确到秒或分钟的时间戳,据此形成sign值来校验有效性。这样可以使一秒或一分钟内的请求是有效的。
若要求较高,则还需要约定一个解密算法,使服务端可以从sign值中解析出发起请求的时间戳。
3) 版本号version
防止重复提交,至少为10位。针对查询接口,版本号只用于日志落地,便于后期日志核查。 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。
4)signature
signature 的规则可以随便定义,按照自己的想法即可,我们以 md5(sort(body)+appSecret)举例,即 先对请求体的字段按照字母a-z顺序进行排序,然后在其尾部拼接appSecret,再经过md5计算出签名,然后用户在请求中添加签名即可。
正常会将appid、timestamp、nonce、signature这四个字段放入请求头中,但我们这里暂时定义规则为只要将body中的key按照大小顺序排好序以后,在拼接上appsecret即可。
三、代码演示
1、传统的http请求
package com.demo.test;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.jfinal.aop.Clear;
import com.jfinal.core.Controller;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import oracle.jdbc.proxy.annotation.Post;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;import javax.servlet.ServletInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;@Clear
public class DemoController extends Controller {private static final long EXPIRE_TIME = 10 * 60 * 1000; // 10分钟的毫秒数public void save() throws IOException {// 检查请求方法是否为 POSTif (!"POST".equalsIgnoreCase(getRequest().getMethod())) {System.out.println("Request method"+ getRequest().getMethod());renderText("Only POST requests are allowed for this method.");return;}// 获取请求头信息String headerValue = getHeader("sign");System.out.println("Header Value: " + headerValue);// 获取请求参数String paramValue = getPara("paramName");System.out.println("Request Parameter Value: " + paramValue);// 获取请求 URLString requestUrl = getRequest().getRequestURL().toString();System.out.println("Request URL: " + requestUrl);// 获取请求体信息String requestBody = getRawRequestBody(getRequest().getInputStream());System.out.println("Request Body: " + requestBody);JSONObject pass = validateSign(headerValue,requestBody);if(pass.getString("success").equals(false)){logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}"+requestUrl+ JSON.toJSONString(paramValue));
// String url =request.getRequestURI()+"?"+ getIpAddress(request)+ JSON.toJSONString(request.getParameterMap());
// CacheKit.put("eam", "url", url);renderJson(pass);}else{//成功逻辑renderJson(pass);}
// renderText("POST request processed");}private JSONObject validateSign(String requestSign,String requestBody) throws IOException {JSONObject reponseJson = new JSONObject();String msg;boolean flag;try {if (StringUtils.isEmpty(requestSign)) {logger.info("签名为空, signature=" + requestSign);msg = "签名为空";reponseJson.put("msg", msg);reponseJson.put("flag", false);return reponseJson;}//时间戳JSONObject jsonObject = JSONObject.parseObject(requestBody);Long now = System.currentTimeMillis();Long requestTimestamp = Long.parseLong( jsonObject.getString("timestamp"));if ((now - requestTimestamp) > EXPIRE_TIME) {logger.info("请求时间超过规定范围时间10分钟, signature=" + requestSign);msg = "请求时间超过规定范围时间10分钟";reponseJson.put("msg", msg);reponseJson.put("flag", false);return reponseJson;}//排序 requestBodyString body = sortJsonString(requestBody);//获取密钥,这里密钥为了方便,写在了配置文件中,可随意定义一些复杂内容如jgjgro4h3h5b5b5b9nnkx0fkeProp p = PropKit.use("config.properties");String secret = p.get("emsSecret");String ls = body + secret;String sign = DigestUtils.md5Hex(ls.getBytes());//混合密钥md5,加密后的sign,用它来和请求传过来的sign对比,如果一致则通过if(StringUtils.equals(sign, requestSign)){logger.info("请求时间超过规定范围时间10分钟, signature=" + requestSign);msg = "请求成功!";reponseJson.put("msg", msg);reponseJson.put("flag", true);return reponseJson;}else {msg = "请求失败!sign不匹配";reponseJson.put("msg", msg);reponseJson.put("flag", false);return reponseJson;}}catch (Exception e){logger.error("Error in execute EmaController. The wrong message is:"+ e.getMessage());}msg = "请求失败";reponseJson.put("msg", msg);reponseJson.put("flag", false);return reponseJson;}private static String sortJsonString(String requestBody) throws JsonProcessingException {// 将 JSON 字符串转换为 JsonNodeObjectMapper objectMapper = new ObjectMapper();JsonNode jsonNode = objectMapper.readTree(requestBody);// 使用 TreeMap 对属性进行排序TreeMap<String, JsonNode> sortedProperties = new TreeMap<>();Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();while (fields.hasNext()) {Map.Entry<String, JsonNode> entry = fields.next();sortedProperties.put(entry.getKey(), entry.getValue());}// 创建新的 JsonNode,按照字母a-z的顺序排列JsonNode sortedJsonNode = objectMapper.valueToTree(sortedProperties);// 将排序后的 JsonNode 转换回 JSON 字符串String sortedJsonString = objectMapper.writeValueAsString(sortedJsonNode);// 输出排序后的 JSON 字符串return sortedJsonString;}// 读取请求体的方法private String getRawRequestBody(ServletInputStream inputStream) {StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;try {bufferedReader = new BufferedReader(new InputStreamReader(inputStream));char[] charBuffer = new char[128];int bytesRead;while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {stringBuilder.append(charBuffer, 0, bytesRead);}} catch (IOException e) {e.printStackTrace();} finally {if (bufferedReader != null) {try {bufferedReader.close();} catch (IOException e) {e.printStackTrace();}}}return stringBuilder.toString();}
}
2、Restful风格
这里由于无法直接获取请求体,因此封装了个方法getRawRequestBody(), 如果是采用restful风格开发,可以直接通过@ReqequstBody Map<String String>params获取请求体,如:
@RestController
public class UserController {@PostMapping("/test")public void createUser(@RequestBody Map<String String> body) {// 在这里处理System.out.pring(body);}
}
获取请求参数如:
@RequestMapping(value = "/test", method = RequestMethod.POST)
@ResponseBody
public StringTestUrl(@RequestParam("username")String username, @RequestParam("pwd")String pwd) {String txt = username + pwd;return txt;
}
暂时先写到这里。。。未完
相关文章:
设计接口时,为其添加签名鉴权---详细教程
一、何为签名 我们知道无论是restful api还是传统接口、亦或是其他形式接口的调用,接口签名都是非常重要的安全机制,它可以确保请求的发起者是经过认证和授权的客户端,同时也可以防止接口被攻击,请求参数被篡改等等。 用大白话来解…...
5G+物联网:连接万物,重塑智慧社区,开启未来生活新纪元,助力智慧社区的革新与发展
一、5G与物联网:技术概述与基础 随着科技的飞速发展,第五代移动通信技术(5G)和物联网(IoT)已经成为当今社会的热门话题。这两项技术作为现代信息社会的核心基础设施,正深刻地改变着人们的生活和…...
[反转链表] [合并两个有序链表][分割链表]
这里写目录标题 反转链表合并两个有序链表分割链表 反转链表 1、题目: 2.思路 思路1:建立一个newHead,取一个节点进行头插。具体做法如下! 建立一个newHead(新头),由于一个节点里面存的是下一个节点的地址,如果取…...
中文数据让LLM变笨?
我这里先贴一下论文的原链接: https://arxiv.org/abs/2401.10286 然后贴一下我翻译标注的下载链接:https://gitee.com/chatpaper/arXiv_top_chinese/blob/master/0801_top/%E4%B8%AD%E6%96%87%E4%BC%9A%E8%AE%A9LLM%E5%8F%98%E7%AC%A8%EF%BC%9F.pdf 先…...
【代码随想录】刷题笔记Day54
前言 差单调栈就结束代码随想录一刷啦,回家二刷打算改用python补充进博客,小涛加油!!! 647. 回文子串 - 力扣(LeetCode) 双指针法 中心点外扩,注意中心点可能有一个元素可能有两个…...
二.Winform使用Webview2在Demo1中实现地址简单校验
Winform使用Webview2在Demo1中实现地址简单校验 往期目录回顾添加对于的简单url验证提示通过上节和本节涉及到的函数有 往期目录 往期相关文章目录 专栏目录 回顾 通过一.Winform使用Webview2(Edge浏览器核心) 创建demo(Demo1)实现回车导航到指定地址 我们已经知道了解决资源…...
从0开始学习C++ 第二十课:模板与泛型编程
第二十课:模板与泛型编程 学习目标: 掌握模板的基本语法和概念。学会使用函数模板来创建可重用的函数。学习如何定义类模板以实现数据结构的泛型。理解模板在C中提供的灵活性和强大功能。 学习内容: 模板的概念: 模板是C中支持…...
pcl之滤波器(一)
pcl滤波器 pcl一共是有十二个主要模块,详细了解可以查看官网。https://pcl.readthedocs.io/projects/tutorials/en/latest/#basic-usage 今天学习一下pcl的滤波器模块。 滤波器模块,官网一共是提供了6个例程,今天先来看第一第二个。 直通…...
java项目性能优化(MyBatis中开启查询缓存及flushCache与useCache的使用)
在java项目中,如果需要大量的DB查询,导致缓存过多,项目运行缓慢,可以设置在select查询时,添加二级缓存的清空。 如果没有去配置flushCache、useCache,那么默认是启用缓存的。 1,flushCache默认…...
Unity3D控制人物移动的多种方法
系列文章目录 unity知识点 文章目录 系列文章目录前言一、人物移动之键盘移动1-1、代码如下1-2、效果 二、人物移动之跟随鼠标点击移动2-1、代码如下2-2、效果 三、人物移动之刚体移动3-1、代码如下3-2、效果 四、人物移动之第一人称控制器移动4-1、代码如下4-2、效果 五、And…...
无人机打击激光器
激光器的应用非常广泛,涵盖了多个领域。以下是一些主要的激光器应用: 医疗领域:激光器在医疗行业中有着重要应用,比如用于激光手术(如眼科手术)、皮肤治疗、牙科治疗、肿瘤治疗等。 工业制造:在…...
Lingo数学建模基础
1.基本运算符 1.1算数运算符 1.2逻辑运算 #not# 否定操作数的逻辑值,一元运算符 #eq# 若两运算数相等,则为true,否则为false #ne# 若两运算数不相等,则为true,否则为false #gt# 若左边运算数严格大于右边,则为true,否则为…...
finalshell连接linux的kali系统
kali的ssh服务似乎是默认关闭的,笔者在玩CentOS系统时可以直接用finalshell完成连接,但kali不行,需要先手动开启ssh服务。 开启kali的ssh服务 输入【ssh start】命令开启ssh服务,可以用【ssh status】命令查看ssh状态,…...
2、Line Charts折线图
可视化时间趋势 现在你已经熟悉了编码环境,是时候学习如何制作自己的图表了! 在本教程中,您将学习足够的Python来创建专业外观的折线图。然后,在接下来的练习中,您将使用您的最新技能处理真实世界的数据集。 本课程数据集夸克网盘下载链接:https://pan.quark.cn/s/a235ac…...
shell脚本获得所有数据库备份(整库备份,表级备份)
数据库备份到天翼云对象存储OBS https://blog.csdn.net/qq_34631220/article/details/135755894 1、获得所有数据库 #!/bin/sh HOSTNAME"ip" #数据库信息 PORT"3306" USERNAME"root" PASSWORD"" DBNAME"yusuan" #数据库…...
REVIT二次开发万能刷
将这两个参数赋予其他参数 步骤2 将来做个可以调控的版本 using System; using System.Collections.Generic; using System.Lin...
JSON简单了解
文章目录 1、JSON介绍2、ES6模版字符串3、JS对象转化为JSON字符串3.1、手动JS对象转化为JSON字符串3.2、自动JS对象转化为JSON字符串 4、JS对象和java互相转换 1、JSON介绍 JSON 概念:JavaScript Object Notation。JavaScript 对象表示法,简单理解JSON是…...
HarmonyOS鸿蒙应用开发( 四、重磅组件List列表组件使用详解)
List列表组件,是一个非常常用的组件。可以说在一个应用中,它的身影无处不在。它包含一系列相同宽度的列表项,适合连续、多行呈现同类数据,如商品列表、图片列表和和文本列表等。ArkUI 框架采用 List 容器组件创建列表(…...
redis优化系列(六)
本期分享redis内存过期策略:过期key的处理 Redis之所以性能强,最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大,会影响持久化或主从同步性能。 可以通过修改配置文件来设置Redis的最大内存: maxmemory 1gb …...
【 Qt 快速上手】-②- Qt 环境搭建
文章目录 1. Qt 开发工具概述1.1 Qt Creator 介绍1.2 Visual Studio 介绍1.3 Eclipse 介绍 2. Qt SDK 的下载与安装2.1 Qt SDK 的下载2.2 Qt SDK 的安装2.3 验证 Qt SDK 安装是否成功2.4 Qt 环境变量配置 1. Qt 开发工具概述 Qt 开发环境需要安装三个部分: C编译器…...
5分钟免费制作专业AI翻唱:AICoverGen完整指南
5分钟免费制作专业AI翻唱:AICoverGen完整指南 【免费下载链接】AICoverGen A WebUI to create song covers with any RVC v2 trained AI voice from YouTube videos or audio files. 项目地址: https://gitcode.com/gh_mirrors/ai/AICoverGen 想让AI帮你翻唱…...
告别网络依赖:CircuitJS1桌面版带你体验离线电路仿真的自由
告别网络依赖:CircuitJS1桌面版带你体验离线电路仿真的自由 【免费下载链接】circuitjs1 Standalone (offline) version of the Circuit Simulator with small modifications based on modified NW.js. 项目地址: https://gitcode.com/gh_mirrors/circ/circuitjs1…...
Gitclaw:封装复杂Git操作,提升开发效率的命令行工具
1. 项目概述:一个为Git操作注入“爪牙”的命令行工具如果你和我一样,日常开发工作重度依赖Git,那你肯定也经历过这样的时刻:面对一个需要多步操作才能完成的复杂Git任务,比如清理多个已合并的分支、批量重写提交历史中…...
3D打印乐高手机支架:低成本打造高清视频会议摄像头方案
1. 项目概述与核心思路如果你和我一样,对视频会议、直播时笔记本自带摄像头那“感人”的画质感到无奈,同时又觉得单独购买一个高品质的网络摄像头是一笔不小的开销,那么这个项目绝对值得你花上一个周末的时间来折腾。它的核心思路非常巧妙&am…...
初创团队如何通过Taotoken的Token Plan实现成本可控的AI应用开发
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 初创团队如何通过Taotoken的Token Plan实现成本可控的AI应用开发 对于预算敏感的初创团队和独立开发者而言,在开发AI应…...
嵌入式动画优化:DMA驱动位图渲染在SAMD21上的实现
1. 项目概述与核心思路如果你玩过嵌入式开发,尤其是想在小小的微控制器屏幕上搞点流畅的动画,大概率会被“卡顿”和“闪屏”折磨过。传统的逐像素绘制,在需要全屏更新时,CPU时间几乎全耗在了等待屏幕刷新上,用户体验大…...
三维重建下半场,拼的全是底层基建实力!
三维重建已从算法创新竞赛正式迈入基础设施比拼新阶段,主流技术路线逐步收敛,单纯算法红利见顶,行业竞争核心转向数据、算力、平台、生态等底层综合能力。当下竞争不再只比模型效果,而是聚焦四大核心基建维度:采集传感…...
017、Docker在TinyML开发中的应用
017 Docker在TinyML开发中的应用 从一次“环境地狱”说起 上个月帮团队调一个STM32上的TinyML推理延迟问题,模型是MobileNetV2量化版,在开发板上跑得好好的,换到同事的Ubuntu 20.04机器上编译,死活链接不上CMSIS-NN库。折腾半天发现他系统里默认的arm-none-eabi-gcc版本是…...
会话管理封装实践:构建安全可扩展的分布式会话系统
1. 项目概述:一个被低估的会话管理利器如果你是一名开发者,尤其是经常需要处理用户登录、权限校验、状态保持这类“脏活累活”的后端或全栈开发者,那么你一定对“会话管理”这四个字又爱又恨。爱的是,它是构建安全、有状态应用的基…...
STM32F407最小系统板DIY全记录:从原理图绘制到PCB打样,手把手带你复刻一块自己的核心板
STM32F407最小系统板DIY全记录:从原理图绘制到PCB打样,手把手带你复刻一块自己的核心板 1. 项目规划与芯片选型 在开始动手之前,我们需要明确几个关键问题:为什么要选择STM32F407?这个芯片适合哪些应用场景?…...
