diff --git a/mes/客户端代码(C#).md b/mes/客户端代码(C#).md new file mode 100644 index 0000000..e60d0e1 --- /dev/null +++ b/mes/客户端代码(C#).md @@ -0,0 +1,124 @@ + +Client3.cs + +``` +using System; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; + +/// 请求体签名客户端。 +public sealed class Client3 +{ + + /// 服务器源。 + /// http://api.group.mes/open/ + public string Source { get; private set; } + + /// 令牌。 + public string Token { get; private set; } + + /// 密钥。 + public string Secret { get; private set; } + + /// 构造函数,创建客户端实例。 + public Client3(string source, string token, string secret) + { + if (string.IsNullOrEmpty(source)) throw new ArgumentException("未指定服务器源。"); + if (string.IsNullOrEmpty(token)) throw new ArgumentException("未指定令牌。"); + if (string.IsNullOrEmpty(secret)) throw new ArgumentException("未指定密钥。"); + + Source = source; + Token = token; + Secret = secret; + } + + /// 发送 POST 请求。 + /// 要调用的方法。 + /// 请求体 JSON 字符串。 + /// HTTP 响应体 + /// + /// + public string Post(string func, string json) + { + // 请求体 + var body = Encoding.UTF8.GetBytes(json ?? ""); + + // 计算签名 + var stamp = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds).ToString(); + var sha256 = Sha256(body); + var sample = Secret + "\n" + stamp + "\n" + sha256; + var signature = Sha256(Encoding.UTF8.GetBytes(sample)); + + // 发起 HTTP 请求 + var url = Source + func; + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = "POST"; + request.ContentType = "application/json"; + request.ContentLength = body.Length; + request.Headers.Add("Token", Token); + request.Headers.Add("Stamp", stamp); + request.Headers.Add("Signature", signature); + request.GetRequestStream().Write(body, 0, body.Length); + var response = request.GetResponse() as HttpWebResponse; + using (var memory = new MemoryStream()) + { + var stream = response.GetResponseStream(); + var buffer = new byte[4096]; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count < 1) break; + memory.Write(buffer, 0, count); + } + return Encoding.UTF8.GetString(memory.ToArray()); + } + } + + /// 计算 SHA256,获取哈希的小写十六进制。 + /// + static string Sha256(byte[] bytes) + { + using (var provider = new SHA256CryptoServiceProvider()) + { + var hash = provider.ComputeHash(bytes); + provider.Clear(); + + const string hex = "0123456789abcdef"; + var chars = new char[hash.Length * 2]; + for (var i = 0; i < hash.Length; i++) + { + var b = bytes[i]; + var offset = i * 2; + chars[offset] = hex[b / 16]; + chars[offset + 1] = hex[b % 16]; + } + return new string(chars); + } + } + +} +``` + +Program.cs + +``` +using System; + +class Program +{ + + static void Main(string[] args) + { + // 准备客户端 + var client3 = new Client3("http://127.0.0.1:8033/open/", "token3", "secret3"); + + // 发起接口请求 + var response = client3.Post("checked", ""); + Console.WriteLine(response); + } + +} + +``` diff --git a/mes/获取认证信息(测试).md b/mes/获取认证信息(测试).md new file mode 100644 index 0000000..a07d8b3 --- /dev/null +++ b/mes/获取认证信息(测试).md @@ -0,0 +1,72 @@ +### 获取认证信息(Checked) + +此接口用于初期对接时验证认证方式时测试用,没有生产用途。 + +此接口需要认证,详情请查看《认证方式》。 + +--- + +### 基本信息 + +方法:POST +URL: http://localhost:8033/open/checked + +--- + +### 请求 + +请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +| :-- | :-- | :-- | :-- | +| strict | bool | 否 | 要求接口必须通过认证,在认证失败时抛出错误信息。默认值:是 | + +示例 1:要求必须认证 + +``` +{ + "data": { + strict: true + } +} +``` + +--- + +### 响应 + +响应字段 + +| 字段 | 类型 | 说明 | +| :-- | :-- | :-- | +| clock | string | 当前服务器时钟。 | +| checked | bool | 认证成功。 | +| app | object | 注册的应用。 | +| app.name | string | 应用的名称。 | + +认证成功时,响应如下: + +``` +{ + "clock": "2023-06-25 20:00:00.000", + "status": "ok", + "data": { + "clock": "2023-06-25 20:00:00.000", + "checked": true, + "app": { + "name": "Demo3" + } + } +} +``` + +服务端和客户端的系统时钟必须准确,若某一端的时钟偏差较大,则会出现以下响应: + +``` +{ + "clock": "2023-06-25 20:00:00.000", + "status": "exception", + "message": "时间戳相差【2000】秒,超出了允许的范围,请检查系统时钟。", + "data": {} +} +``` diff --git a/mes/认证方式.md b/mes/认证方式.md new file mode 100644 index 0000000..8aa3006 --- /dev/null +++ b/mes/认证方式.md @@ -0,0 +1,122 @@ +### 认证方式 + +除了少部分的查询接口以外,大部分开放接口都需要认证,认证失败时候无法获取内容。 + +目前已启用的认证方式有: +- 基本认证 +- 请求体签名认证 + +--- + +### 基本认证 + +基本认证使用固定的明文在网络中传输认证信息,存在被劫持的风险,因此不建议使用。 + +基本认证仅需使用一个 `token` 参数,加入 HTTP 头部即可,以下为 HTTP 请求示例: + +``` +POST /open/checked HTTP/1.1 +Host: 127.0.0.1 +Content-Type: application/json +Content-Length: 30 +Token: token1 + +{ "data": { "strict": true } } +``` + +认证成功时响应内容由接口控制;认证失败时会得到以下响应体: + +``` +{ + "status": "exception", + "message": "令牌不存在。", + "data": {} +} +``` + +--- + +### 请求体签名认证 + +使用请求体签名认证方式时,所有请求必须是 POST 方法,应用注册后可取到两个值: +- token:请求令牌,此值为固定值,需明文传输 +- secret:哈希盐,此值用于计算哈希时加盐 + +HTTP 请求中与认证有关的字段有 3 个: +- token:请求令牌,此参数为固定值 +- stamp:当前 UTC 毫秒时间戳 +- signature:签名,此参数为动态值 + +#### 签名步骤 + +根据当前时钟生成 UTC 时毫秒间戳,获取字符串: + +``` +// JavaScript 生成时间戳 +var stamp = String(Number(new Date())); + +// C# 生成时间戳 +var stamp = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds).ToString(); +``` + +获取请求体的字符串: + +``` +{ "data": { "strict": true } } +``` + +使用 UTF-8 对请求体的文本进行编码,获取到 30 个字节,这与 HTTP 请求头中的 `Content-Length: 30` 一致,计算 SHA256,获取小写的十六进制: + +``` +// sha256 +bd1f3e0163a759712739476ef49425ea93753fe690f5edd589c0e43bd0d1f653 +``` + +将请求体哈希、时间戳和 secret 使用换行符拼接成一个字符串: + +``` +var secret = "secret3"; +var stamp = "1687723200000"; +var sha256 = "bd1f3e0163a759712739476ef49425ea93753fe690f5edd589c0e43bd0d1f653"; +var sample = secret + "\n" + stamp + "\n" + sha256; +``` + +拼接后,得到待签名字符串: + +``` +secret3 +1687723200000 +bd1f3e0163a759712739476ef49425ea93753fe690f5edd589c0e43bd0d1f653 +``` + +对待签名字符串进行 UTF-8 编码后,计算 SHA256,即为签名: + +``` +64235f1ae5900039b5e5c370aebbe8081b8b24b08b2bc3806a9a359304fc1e3b +``` + +#### 请求示例 + +将令牌(Token)、时间戳(Stamp)和签名(Signature)加入 HTTP 请求头中,即可发起请求。 + +``` +POST /open/checked HTTP/1.1 +Host: 127.0.0.1 +Content-Type: application/json +Content-Length: 30 +Token: token3 +Stamp: 1687723200000 +Signature: 64235f1ae5900039b5e5c370aebbe8081b8b24b08b2bc3806a9a359304fc1e3b + +{ "data": { "strict": true } } +``` + +认证成功时响应内容由接口控制;认证失败时会得到以下响应体: + +``` +{ + "status": "exception", + "message": "令牌不存在。", + "data": {} +} +```