数据在网络传输过程中,容易被抓包。如果使用的是HTTP协议的请求/响应(Request OR Response),它是明文传输的,都是可以被截获、篡改、重放(重发)的。所以需要进行数据的加密验签,所以需要考虑以下几点。
常见的方式,就是对关键字段加密。比如查询订单接口,就可以对订单号进行加密。一般常用的加密算法对称加密算法(如:AES),或者哈希算法处理(如:MD5)
对称加密:加密和解密使用相同秘钥的加密算法
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
非对称加密:非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥和私钥是成对存在的,如果用公钥对数据加密,只有对应的私钥才能解密。 (非对称加密是更安全的做法,加密是算法RSA或SM2)
非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
加签验签:使用Hash算法(如 MD5或者SHA-256)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,得到报文对应的sign
加签:用Hash函数把原始报文生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名。通常来说呢,请求方会把「数字签名和报文原文」一并发送给接收方。
验签:接收方拿到原始报文和数字签名后,用「同一个Hash函数」从报文中生成摘要A。另外,用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。
请求参数:
| 字段 | 类型 | 必传 | 说明 |
| sign | String | 是 | 接口签名,用户接口验证 |
| app_id | String | 是 | 开放平台的APP_ID,例如:1234 |
| date_time | String | 是 | 当前时间戳 |
| key | String | 是 | 开发平台的APP_KEY,例如:XA12#Da |
| name | String | 否 | 业务参数 |
| age | Integer | 否 | 业务参数 |
业务参数消息体数据格式:Content-Type 指定为 application/json
1.将请求参数中除sign外的多个键值对,根据键按照字典序排序,并按照"key1=value1&key2=value2..."的格式拼成一个字符串
String sortStr=" age=11&app_id=1234&date_time=1656926899731&name=xxx"
2.将key拼接在第一步中排序后的字符串后面得到待签名字符串
String sortStr ="age=11&app_id=1234&date_time=1656926899731&name=xxxkey=XA12#Da"
3.使用md5算法加密待加密字符串并转为大写即为sign
String sign ="57A132B7585F77B1948812275BE945B8"
4.将sign添加到请求参数中
https://www.baidu.com/test/get?age=11&app_id=1234&date_time=1656926899731&name=xxx&sign=57A132B7585F77B1948812275BE945B8
需要注意以下重要规则:
◆ 请求参数中有中文时,中文需要经过url编码,但计算签名时不需要;
◆ 请求参数的值为空则不参与签名;
◆ 参数名区分大小写;
◆ sign参数不参与签名;
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.6</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
服务端拦截器:
package com.cykj.card.filter;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.util.concurrent.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import org.apache.groovy.util.concurrent.concurrentlinkedhashmap.Weighers;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
/**
* @author 小影
* @create 2022-07-04 10:59
* @describe:
*/
@Slf4j
@Order(1)
@WebFilter
@Component
public class ReqFilter implements Filter {
// 热点缓存
public static ConcurrentLinkedHashMap<String, String> cache = new ConcurrentLinkedHashMap.Builder<String, String>()
.maximumWeightedCapacity(30).weigher(Weighers.singleton()).build();
/**
* 初始化
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// key=APPID ,value=密钥 在数据库中加载出来,这里为了演示写死
cache.put("1234", "XA12#Da");
}
/**
* 签名验证时间(TIMES =分钟 * 秒 * 毫秒)
* 当前设置为:5分钟有效期
*/
protected static final Integer TIMES = 5 * 60 * 1000;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断请求方式
String method = request.getMethod();
if ("POST".equals(method)) {
log.info("POST请求进入...");
// 获取请求Body参数,需要使用 BodyReaderHttpServletRequestWrapper进行处理
// 否则会出现异常:I/O error while reading input message; nested exception is java.io.IOException: Stream closed
// 原因就是在拦截器已经读取了请求体中的内容,这时候Request请求的流中已经没有了数据
// 解决流只能读取一次的问题:先读取流,然后在将流重新写进去就行了
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
String body = HttpHelper.getBodyString(requestWrapper);
String bodyString = URLDecoder.decode(body, "utf-8");
if (StrUtil.isEmpty(bodyString)) {
Map<String, Object> map = new HashMap<>();
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("utf-8");
map.put("code", 7000);
map.put("msg", "请求参数不能为空");
writer.print(JSONObject.toJSONString(map));
writer.close();
return;
}
// 解析参数转JSON格式
// bodyString = "{" + bodyString.replace("&", "',").replace("=", ":'") + "'}";
JSONObject jsonObject = JSONObject.parseObject(bodyString);
// 验签
boolean validation = validation(jsonObject, response);
if (!validation) {
return;
}
log.info("POST请求验签通过...");
// 放行
chain.doFilter(request, response);
}
if ("GET".equals(method)) {
log.info("GET请求进入...");
// 获取请求参数
Map<String, String> allRequestParam = getAllRequestParam(request);
Set<Map.Entry<String, String>> entries = allRequestParam.entrySet();
// 参数转JSON格式
JSONObject jsonObject = new JSONObject();
entries.forEach(key -> {
jsonObject.put(key.getKey(), key.getValue());
});
// 验签
boolean validation = validation(jsonObject, response);
if (!validation) {
return;
}
log.info("GET请求验签通过...");
// 放行
chain.doFilter(request, response);
}
}
/**
* 验签
*
* @param body 请求参数
* @param response
* @return
* @throws IOException
*/
private boolean validation(JSONObject body, HttpServletResponse response) throws IOException {
// 拿出请求签名
String sign = body.getString("sign");
body.remove("sign");
// 根据APPID查询的密钥进行重签
String sign1 = getSign(body);
Map<String, Object> map = new HashMap<>();
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("utf-8");
// 校验签名
if (!StringUtils.equals(sign1, sign)) {// APPID查询的密钥进行签名 和 用户签名进行比对
map.put("code", 10000);
map.put("msg", "签名错误");
writer.print(JSONObject.toJSONString(map));
return false;
}
// 校验签名是否失效
long thisTime = System.currentTimeMillis() - body.getLong("date_time");
if (thisTime > TIMES) {// 比对时间是否失效
map.put("code", 10000);
map.put("msg", "签名失效");
writer.print(JSONObject.toJSONString(map));
return false;
}
return true;
}
/**
* 计算签名
*
* @param params
* @return
*/
public static String getSign(JSONObject params) {
// 从缓存中获取密钥
String key = cache.get(params.getString("app_id"));
if (StringUtils.isBlank(key)) {
key = "XA12#Da";// 如果为nulll密钥就从DB中查询,这里演示就写死
cache.put(params.getString("app_id"), key);// 放入缓存
}
// 参数进行字典排序
String sortStr = getFormatParams(params);
// 将密钥key拼接在字典排序后的参数字符串中,得到待签名字符串。
sortStr += "key=" + key;
// sortStr += "key=xxxxx";
// 使用md5算法加密待加密字符串并转为大写即为sign
String sign = SecureUtil.md5(sortStr).toUpperCase();
return sign;
}
/**
* 参数字典排序
*
* @param params
* @return
*/
private static String getFormatParams(Map<String, Object> params) {
List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
public int compare(Map.Entry<String, Object> arg0, Map.Entry<String, Object> arg1) {
return (arg0.getKey()).compareTo(arg1.getKey());
}
});
String ret = "";
for (Map.Entry<String, Object> entry : infoIds) {
ret += entry.getKey();
ret += "=";
ret += entry.getValue();
ret += "&";
}
return ret;
}
/**
* 获取客户端GET请求中所有的请求参数
*
* @param request
* @return
*/
private Map<String, String> getAllRequestParam(final HttpServletRequest request) {
Map<String, String> res = new HashMap<String, String>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
//如果字段的值为空,判断若值为空,则删除这个字段>
if (null == res.get(en) || "".equals(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
}
防止流丢失:
package com.cykj.card.filter;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
/**
* @author 小影
* @create 2022-07-04 10:59
* @describe:
*/
public class BodyReaderHttpServletRequestWrapper extends
HttpServletRequestWrapper {
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
System.out.println("-------------------------------------------------");
Enumeration e = request.getHeaderNames() ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getHeader(name);
System.out.println(name+" = "+value);
}
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
@Override
public String getHeader(String name) {
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}
@Override
public Enumeration<String> getHeaders(String name) {
return super.getHeaders(name);
}
}
获取POST请求的Body中的参数:
package com.cykj.card.filter;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* @author 小影
* @create 2022-07-04 10:59
* @describe:
*/
public class HttpHelper {
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("sb = " + sb);
return sb.toString();
}
}
客户端请求模拟:
/**
* @author 小影
* @create 2022-07-04 17:01
* @describe:
*/
@SpringBootTest
public class TestHttpReq {
public static String secretKey = "XA12#Da";
@Test
public void testPost() {
JSONObject data = new JSONObject();
data.put("name", "xxx");
data.put("age", "11");
data.put("app_id", "1234");
long dateTime = new Date().getTime();
data.put("date_time", "1656926899731");
String sign = getSign(data);
//修改密钥为数据加密后加密串
data.put("sign", sign);
HttpResponse response = HttpRequest.post("localhost:8083/card/ss").form(data).contentType("application/json").execute();
Object jsonObject = JSONObject.parse(response.body().trim());
System.out.println("jsonObject = " + jsonObject);
}
@Test
public void testGet() {
String url = "localhost:8083/card/ss";
long dateTime = new Date().getTime();
//传入参数
JSONObject data = new JSONObject();
data.put("name", "123");
data.put("age", "sss");
data.put("app_id", "1234");
data.put("date_time", dateTime);
String sign = getSign(data);
url = url + "?name=123&age=sss&appid=1234&dateTime=" + dateTime + "&sign=" + sign;
HttpResponse response = HttpRequest.get(url).execute();
System.out.println("response.body() = " + response.body().trim());
}
/**
* 计算签名
*
* @param params
* @return
*/
public static String getSign(JSONObject params) {
String sortStr = getFormatParams(params);
System.out.println("sortStr = " + sortStr);
//第二步:将tradeKey拼接在1中排序后的字符串后面得到待签名字符串。
sortStr += "key="+ secretKey;
System.out.println("sortStr = " + sortStr);
//sortStr += "key=BF1BDE5A649724056F904A9335B1C1C9";
//第三步:使用md5算法加密待加密字符串并转为大写即为sign
String sign = DigestUtils.md5DigestAsHex(sortStr.getBytes()).toUpperCase();
System.out.println("sign = " + sign);
return sign;
}
/**
* 字典排序
* 获得参数格式化字符串
* 参数名按字典排序,小写在后面
*/
private static String getFormatParams(Map<String, Object> params) {
List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(params.entrySet());
Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
public int compare(Map.Entry<String, Object> arg0, Map.Entry<String, Object> arg1) {
return (arg0.getKey()).compareTo(arg1.getKey());
}
});
String ret = "";
for (Map.Entry<String, Object> entry : infoIds) {
ret += entry.getKey();
ret += "=";
ret += entry.getValue();
ret += "&";
}
ret = ret.substring(0, ret.length() - 1);
return ret;
}
}
这是小编在开发学习使用和总结的小Demo, 这中间或许也存在着不足,希望可以得到大家的理解和建议。如有侵权联系小编!
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.