Http超文本传输协议。
超文本:包括:文字,图片,音频,视频等。
传输:客户端向服务端发东西,服务端向客户端发东西。
协议:三方协议。怎么传,错误处理。责权利。
总结:在计算机世界中:两点(客户端,服务端)直接传输 超文本的一个约定 、 规范。应用层协议。
www.demo.com
浏览器缓存
操作系统缓存。(hosts,有可能被篡改)
LDNS(本地域名服务器)
根域名(cn,com):com
主域名服务器(demo.com)
具体域名服务器(www.demo.com)-->ip
缓存(ttl,过期时间)
RESTful(Representational State Transfer)资源表现层状态转移。
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
接口URI动词设计,这些动词就是HTTP的操作方式,无需提现在URI中。
get 查询:单条记录,列表。
post: 新建资源。(新增订单)
put: 更新(全量更新)
patch: 更新(局部更新)
delete:删除。
例如:查询用户信息,再加上版本号
GET /user/v1.0/{userId}
版本号可以在HTTP请求头信息的Accept字段中进行区分
Accept: vnd.example-com.foo+json; version=v1.0
接口范例
应用添加
path:/app
method:post
请求参数:
{
"enAppName":"英文应用名称",
"chAppName":"中文应用名称",
"operator":"操作人",
"dataSourceType":"1",
}
返回参数:
{
"code":0,
"message":"消息内容",
"data":{}
}
应用修改
path:/app
method:put
请求参数:
{
"appId":"uuid",//新增不填,修改必填
"enAppName":"英文应用名称",
"chAppName":"中文应用名称",
"operator":"操作人"
}
返回参数:
{
"code":0,
"message":"消息内容",
"data":{}
}
应用修改(局部)
path:/app/{appId}
method:patch
请求参数:
{
"chAppName":"中文应用名称",
}
返回参数:
{
"code":0,
"message":"消息内容",
"data":{}
}
应用删除
path:/app/{appId}
method:delete
请求参数:
无
返回参数:
{
"code":0,
"message":"消息内容",
"data":{}
}
应用列表
path:/app/list?pageNumber=1&pageSize=10&queryStr=查询条件&sortrule=+a字段,-b字段
method:get
返回参数:
{
"code":0,
"message":"消息内容",
"data":{
"list":[
{
"appId":"uuid",
"enAppName":"英文应用名称",
"chAppName":"中文应用名称",
"dataSourceType":"1",
"operator":"操作人",
"createTime":毫秒长整型,
"updateTime":毫秒长整型
},
{
"appId":"uuid",
"enAppName":"英文应用名称",
"chAppName":"中文应用名称",
"dataSourceType":"1",
"operator":"操作人",
"createTime":毫秒长整型,
"updateTime":毫秒长整型
}
],
"pageNumber":1,//第几页,从1开始。
"pageSize":10,//页面大小
"totalRecord":100,//总数
}
}
应用详情
path:/app/{appId}
method:get
返回参数:
{
"code":0,
"message":"消息内容",
"data":{
"appId":"uuid",
"enAppName":"英文应用名称",
"chAppName":"中文应用名称",
"dataSourceType":"1",
"operator":"操作人",
"createTime":毫秒长整型,
"upateTime":毫秒长整型
}
}
消费端:采用参数签名方式,对请求的入参进行签名,签名值和参数一同传给服务端。
服务端:对请求中的参数进行签名,用签名值和消费端传来的签名值对比一致即可。
签名实现demo
@Slf4j
public class SignUtils {
public static String appSecret = "SP000001";
// 校验 签名
public static Map checkSign(Map map){
Map resultMap = new HashMap();
String sign = (String) map.get("sign");
map.remove("sign");
long timestamp = Long.parseLong((String) map.get("timestamp"));
// 让接口在有效期内访问
long time = System.currentTimeMillis() - timestamp;
if (time > 1000 * 60 * 3){
resultMap.put("code", "0001");
resultMap.put("msg", "签名过期,请重新生成!");
return resultMap;
}
// 生成sign
String s = generatorSign(map);
if (s.equals(sign)){
resultMap.put("code", "0000");
resultMap.put("msg", "SUCCESS");
return resultMap;
}else {
resultMap.put("code", "0002");
resultMap.put("msg", "签名错误");
return resultMap;
}
}
// 根据map生成签名
public static String generatorSign(Map map){
map.remove("sign");
// 排序
Map stringObjectMap = sortMapByKey(map);
// 转格式
Set> entries = stringObjectMap.entrySet();
StringBuilder sb = new StringBuilder();
for (Map.Entry e : entries){
sb.append(e.getKey()+","+e.getValue()).append("#");
}
// 组装secret 在参数的后面 添加 secret
sb.append("secret").append(appSecret);
log.info("签名前内容:" + sb.toString());
// 生成签名
return MD5Util.md5(sb.toString());
// sha256生成 签名
// return Sha256Utils.getSHA256(sb.toString());
}
public static Map sortMapByKey(Map map){
Map sortMap = new TreeMap<>(new MyMapComparator());
sortMap.putAll(map);
return sortMap;
}
static class MyMapComparator implements Comparator{
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("appId","1");
map.put("name","2");
map.put("urlParam","3");
map.put("timestamp","1651668355151");
String s = generatorSign(map);
System.out.println(s);
}
}
这个接口请求不可篡改后,另一个问题又来了。如果一笔交易被同样的请求多次请求,会导致交易重复出现。于是出现了两种解决方式。
消费端:传入timestamp为当前时间。
服务端:服务端接收到请求后,会对timestamp做一次校验,超出规定时间(如:2 min)范围的请求,被视为非法请求。
实现已在上面的demo中包含。
如果安全级别非常高了。可能存在2分钟内,被攻击者重复调用的情况,此时则需要将请求的url缓存到Redis或者持久化到数据库。消费端每次调用此接口都需要先查询此url是否存在与缓存或者数据库中。存在则被定义为非法请求。
实现未写出,后续更新