Skip to content

Commit c7373da

Browse files
committed
add aesbody for get aes cryptbody
1 parent 9a8d141 commit c7373da

5 files changed

Lines changed: 297 additions & 0 deletions

File tree

Attributes/AesBodyAttribute.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using Microsoft.AspNetCore.Mvc;
3+
using SimApi.ModelBinders;
4+
5+
namespace SimApi.Attributes;
6+
7+
[AttributeUsage(AttributeTargets.Parameter)]
8+
public class AesBodyAttribute : ModelBinderAttribute
9+
{
10+
public Type KeyProvider { get; set; } = typeof(AesBodyProviderBase);
11+
12+
public AesBodyAttribute()
13+
{
14+
// 指定使用自定义的模型绑定器
15+
BinderType = typeof(AesBodyModelBinder);
16+
Name = KeyProvider.FullName;
17+
}
18+
}

Attributes/SimApiSignAttribute.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Linq;
3+
using Microsoft.AspNetCore.Mvc.Filters;
4+
using Microsoft.Extensions.Caching.Distributed;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using SimApi.Exceptions;
7+
using SimApi.Helpers;
8+
using SimApi.ModelBinders;
9+
10+
namespace SimApi.Attributes;
11+
12+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
13+
public class SimApiSignAttribute : ActionFilterAttribute
14+
{
15+
protected Type KeyProvider { get; set; } = typeof(SimApiSignProviderBase);
16+
17+
public override void OnActionExecuting(ActionExecutingContext context)
18+
{
19+
if (context.HttpContext.RequestServices.GetService(KeyProvider) is not SimApiSignProviderBase keyProvider)
20+
{
21+
throw new SimApiException(400, "未配置签名器");
22+
}
23+
24+
string? appId = null;
25+
if (!string.IsNullOrEmpty(keyProvider.AppIdName))
26+
{
27+
appId = context.HttpContext.Request.Query[keyProvider.AppIdName]
28+
.FirstOrDefault() ?? context.HttpContext.Request.Headers[keyProvider.AppIdName]
29+
.FirstOrDefault();
30+
31+
if (string.IsNullOrEmpty(appId))
32+
{
33+
throw new SimApiException(400, $"获取{keyProvider.AppIdName}失败");
34+
}
35+
}
36+
37+
// 4. 通过接口获取密钥(解耦的核心:不再直接依赖数据库)
38+
var key = keyProvider.GetKey(appId);
39+
if (string.IsNullOrEmpty(key))
40+
{
41+
throw new SimApiException(400, "获取签名KEY失败");
42+
}
43+
44+
var timestamp = context.HttpContext.Request.Query[keyProvider.TimestampName]
45+
.FirstOrDefault() ?? context.HttpContext.Request.Headers[keyProvider.TimestampName]
46+
.FirstOrDefault();
47+
if (string.IsNullOrEmpty(timestamp))
48+
{
49+
throw new SimApiException(400, $"{keyProvider.TimestampName}不能为空");
50+
}
51+
52+
var nonce = context.HttpContext.Request.Query[keyProvider.NonceName]
53+
.FirstOrDefault() ?? context.HttpContext.Request.Headers[keyProvider.NonceName]
54+
.FirstOrDefault();
55+
if (string.IsNullOrEmpty(nonce))
56+
{
57+
throw new SimApiException(400, $"{keyProvider.NonceName}不能为空");
58+
}
59+
60+
if (!int.TryParse(timestamp, out var ts))
61+
{
62+
throw new SimApiException(400, $"{keyProvider.TimestampName}格式错误");
63+
}
64+
65+
if (keyProvider.QueryExpires != 0)
66+
{
67+
if (ts > SimApiUtil.TimestampNow + 2)
68+
{
69+
throw new SimApiException(400, "请校准本地时间");
70+
}
71+
72+
if (ts + keyProvider.QueryExpires < SimApiUtil.TimestampNow)
73+
{
74+
throw new SimApiException(400, "请求已过期");
75+
}
76+
77+
if (keyProvider.DuplicateRequestProtection)
78+
{
79+
var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
80+
if (cache.GetString("SignQuery:" + nonce) == null)
81+
{
82+
cache.SetString("SignQuery:" + nonce, timestamp, new DistributedCacheEntryOptions()
83+
{
84+
SlidingExpiration = TimeSpan.FromSeconds(keyProvider.QueryExpires + 2)
85+
});
86+
}
87+
else
88+
{
89+
throw new SimApiException(400, "重复请求");
90+
}
91+
}
92+
}
93+
94+
var signStr = string.Empty;
95+
foreach (var item in keyProvider.SignFields)
96+
{
97+
signStr += $"{item}=";
98+
signStr += context.HttpContext.Request.Query[item]
99+
.FirstOrDefault() ?? context.HttpContext.Request.Headers[item]
100+
.FirstOrDefault();
101+
signStr += "&";
102+
}
103+
104+
signStr += $"{keyProvider.TimestampName}={ts}&{keyProvider.NonceName}={nonce}&{key}";
105+
var sign = context.HttpContext.Request.Query[keyProvider.SignName]
106+
.FirstOrDefault() ?? context.HttpContext.Request.Headers[keyProvider.SignName]
107+
.FirstOrDefault();
108+
if (SimApiUtil.Md5(signStr) != sign)
109+
{
110+
throw new SimApiException(400, "签名错误");
111+
}
112+
}
113+
}

ModelBinders/AesBodyModelBinder.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Text.Json;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Mvc.ModelBinding;
8+
using SimApi.Communications;
9+
using SimApi.Helpers;
10+
11+
namespace SimApi.ModelBinders;
12+
13+
public class AesBodyModelBinder : IModelBinder
14+
{
15+
public async Task BindModelAsync(ModelBindingContext bindingContext)
16+
{
17+
var request = bindingContext.HttpContext.Request;
18+
request.EnableBuffering();
19+
using var reader = new StreamReader(request.Body, leaveOpen: true);
20+
var requestBody = await reader.ReadToEndAsync();
21+
request.Body.Position = 0;
22+
23+
if (string.IsNullOrEmpty(requestBody))
24+
{
25+
bindingContext.ModelState.AddModelError("", "请求体不能为空");
26+
bindingContext.Result = ModelBindingResult.Failed();
27+
return;
28+
}
29+
30+
SimApiOneFieldRequest<string>? aesRequest;
31+
try
32+
{
33+
aesRequest = JsonSerializer.Deserialize<SimApiOneFieldRequest<string>>(requestBody, SimApiUtil.JsonOption);
34+
}
35+
catch (JsonException ex)
36+
{
37+
bindingContext.ModelState.AddModelError("", $"请求体格式错误:{ex.Message}");
38+
bindingContext.Result = ModelBindingResult.Failed();
39+
return;
40+
}
41+
42+
if (aesRequest == null || string.IsNullOrEmpty(aesRequest.Data))
43+
{
44+
bindingContext.ModelState.AddModelError("", "请求体缺少密文Data字段");
45+
bindingContext.Result = ModelBindingResult.Failed();
46+
return;
47+
}
48+
49+
// 3. 根据配置获取appId(参考上一节代码)
50+
var keyProvider =
51+
bindingContext.HttpContext.RequestServices.GetService(Type.GetType(bindingContext.BinderModelName!)!) as
52+
AesBodyProviderBase;
53+
string? appId = null;
54+
if (!string.IsNullOrEmpty(keyProvider!.AppIdName))
55+
{
56+
appId = bindingContext.HttpContext.Request.Query[keyProvider.AppIdName]
57+
.FirstOrDefault() ?? bindingContext.HttpContext.Request.Headers[keyProvider.AppIdName]
58+
.FirstOrDefault();
59+
60+
if (string.IsNullOrEmpty(appId))
61+
{
62+
bindingContext.ModelState.AddModelError("",
63+
$"未找到{keyProvider.AppIdName}");
64+
bindingContext.Result = ModelBindingResult.Failed();
65+
return;
66+
}
67+
}
68+
69+
// 4. 通过接口获取密钥(解耦的核心:不再直接依赖数据库)
70+
var key = keyProvider.GetKey(appId);
71+
if (string.IsNullOrEmpty(key))
72+
{
73+
bindingContext.ModelState.AddModelError("", "获取密钥失败(应用不存在或密钥未配置)");
74+
bindingContext.Result = ModelBindingResult.Failed();
75+
return;
76+
}
77+
78+
// 5. 解密和反序列化(保持原有逻辑)
79+
try
80+
{
81+
var jsonStr = SimApiAesUtil.Decrypt(aesRequest.Data, key);
82+
if (string.IsNullOrEmpty(jsonStr))
83+
{
84+
bindingContext.ModelState.AddModelError("", "解密失败");
85+
bindingContext.Result = ModelBindingResult.Failed();
86+
return;
87+
}
88+
89+
var targetType = bindingContext.ModelType;
90+
var deserializedModel = JsonSerializer.Deserialize(jsonStr, targetType, SimApiUtil.JsonOption);
91+
if (deserializedModel == null)
92+
{
93+
bindingContext.ModelState.AddModelError("", "反序列化失败");
94+
bindingContext.Result = ModelBindingResult.Failed();
95+
return;
96+
}
97+
98+
bindingContext.Result = ModelBindingResult.Success(deserializedModel);
99+
}
100+
catch (Exception ex)
101+
{
102+
bindingContext.ModelState.AddModelError("", $"处理异常:{ex.Message}");
103+
bindingContext.Result = ModelBindingResult.Failed();
104+
}
105+
}
106+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace SimApi.ModelBinders;
2+
3+
/// <summary>
4+
/// 密钥提供器接口(抽象密钥获取逻辑)
5+
/// </summary>
6+
public abstract class AesBodyProviderBase
7+
{
8+
public string? AppIdName { get; set; } = "appId";
9+
10+
11+
/// <summary>
12+
/// 根据appId获取对应的密钥
13+
/// </summary>
14+
/// <param name="appId">应用ID</param>
15+
/// <returns>密钥(返回null表示获取失败)</returns>
16+
public abstract string? GetKey(string? appId);
17+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace SimApi.ModelBinders;
2+
3+
public abstract class SimApiSignProviderBase
4+
{
5+
/// <summary>
6+
/// appId字段的名称
7+
/// </summary>
8+
public string? AppIdName { get; set; } = "appId";
9+
10+
/// <summary>
11+
/// 时间戳的字段名
12+
/// </summary>
13+
public string TimestampName { get; set; } = "timestamp";
14+
15+
/// <summary>
16+
/// 随机字符串的字段名
17+
/// </summary>
18+
public string NonceName { get; set; } = "nonce";
19+
20+
/// <summary>
21+
/// 签名的字段名
22+
/// </summary>
23+
public string SignName { get; set; } = "sign";
24+
25+
/// <summary>
26+
/// 请求过期时间, 如果为0, 不校验timestamp
27+
/// </summary>
28+
public int QueryExpires { get; set; } = 5;
29+
30+
/// <summary>
31+
/// 如果开启,必须配置redis, 每次请求将会缓存nonce
32+
/// </summary>
33+
public bool DuplicateRequestProtection { get; set; } = true;
34+
35+
public string[] SignFields { get; set; } = ["appId"];
36+
37+
/// <summary>
38+
/// 根据appId获取对应的密钥
39+
/// </summary>
40+
/// <param name="appId">应用ID</param>
41+
/// <returns>密钥(返回null表示获取失败)</returns>
42+
public abstract string? GetKey(string? appId);
43+
}

0 commit comments

Comments
 (0)