设计接口时,为其添加签名鉴权---详细教程
一、何为签名
我们知道无论是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编译器…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
