王厅
1 year ago
3 changed files with 318 additions and 0 deletions
@ -0,0 +1,124 @@ |
|||||
|
|
||||
|
Client3.cs |
||||
|
|
||||
|
``` |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Net; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
|
||||
|
/// <summary>请求体签名客户端。</summary> |
||||
|
public sealed class Client3 |
||||
|
{ |
||||
|
|
||||
|
/// <summary>服务器源。</summary> |
||||
|
/// <value>http://api.group.mes/open/</value> |
||||
|
public string Source { get; private set; } |
||||
|
|
||||
|
/// <summary>令牌。</summary> |
||||
|
public string Token { get; private set; } |
||||
|
|
||||
|
/// <summary>密钥。</summary> |
||||
|
public string Secret { get; private set; } |
||||
|
|
||||
|
/// <summary>构造函数,创建客户端实例。</summary> |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
/// <summary>发送 POST 请求。</summary> |
||||
|
/// <param name="func">要调用的方法。</param> |
||||
|
/// <param name="json">请求体 JSON 字符串。</param> |
||||
|
/// <returns>HTTP 响应体</returns> |
||||
|
/// <exception cref="ArgumentNullException"></exception> |
||||
|
/// <exception cref="Exception"></exception> |
||||
|
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()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>计算 SHA256,获取哈希的小写十六进制。</summary> |
||||
|
/// <exception cref="ArgumentException"></exception> |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
``` |
@ -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": {} |
||||
|
} |
||||
|
``` |
@ -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": {} |
||||
|
} |
||||
|
``` |
Loading…
Reference in new issue