fix(微信): 验签接口仅保留 GET 并原样返回 echostr
- /apiwechat/verification 移除 HEAD/POST,仅保留 GET 验签
- 验签通过后从 Query 原样返回 echostr 纯文本
- 扫码回调测试改走 /wechat/mp/{appId}
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0a5bd75618
commit
6c2e095196
@ -16,13 +16,12 @@ namespace SimpleAdmin.Api.IntegrationTests;
|
||||
public class WeChatRouteIntegrationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task WeChatLegacyVerification_ShouldRegisterGetAndPostMethods()
|
||||
public async Task WeChatLegacyVerification_ShouldRegisterGetOnly()
|
||||
{
|
||||
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: ConfigureWeChatFallback);
|
||||
|
||||
var methods = host.GetHttpMethods("apiwechat/verification");
|
||||
methods.Should().Contain("GET");
|
||||
methods.Should().Contain("POST");
|
||||
methods.Should().BeEquivalentTo(["GET"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -61,14 +60,27 @@ public class WeChatRouteIntegrationTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WeChatValidateLegacy_Head_ShouldReturn200()
|
||||
public async Task WeChatValidateLegacy_ShouldReturnEchostrAsPlainTextWithoutJsonWrapper()
|
||||
{
|
||||
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: ConfigureWeChatFallback);
|
||||
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services =>
|
||||
{
|
||||
ConfigureWeChatFallback(services);
|
||||
services.PostConfigure<WeChatMpOptions>(options => options.Token = "test-token");
|
||||
});
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, "/apiwechat/verification");
|
||||
var response = await host.Client.SendAsync(request);
|
||||
const string timestamp = "1400000000";
|
||||
const string nonce = "nonce";
|
||||
const string echostr = "5938472619053847261";
|
||||
var signature = SignWeChat("test-token", timestamp, nonce);
|
||||
|
||||
var response = await host.Client.GetAsync(
|
||||
$"/apiwechat/verification?signature={signature}×tamp={timestamp}&nonce={nonce}&echostr={echostr}");
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
response.Content.Headers.ContentType?.MediaType.Should().Be("text/plain");
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
body.Should().Be(echostr);
|
||||
body.Should().NotStartWith("{");
|
||||
}
|
||||
|
||||
private static void ConfigureWeChatFallback(IServiceCollection services)
|
||||
|
||||
@ -61,7 +61,7 @@ public class WeChatScanLoginIntegrationTests
|
||||
|
||||
using var postContent = new StringContent(scanXml, Encoding.UTF8, "text/xml");
|
||||
var postResponse = await host.Client.PostAsync(
|
||||
$"/apiwechat/verification?signature={signature}×tamp={timestamp}&nonce={nonce}",
|
||||
$"/wechat/mp/wx-test?signature={signature}×tamp={timestamp}&nonce={nonce}",
|
||||
postContent);
|
||||
|
||||
postResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
@ -23,42 +23,36 @@ public partial class AuthService
|
||||
LazyServiceProvider.GetRequiredService<ILogger<AuthService>>();
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号接入验签(GET)
|
||||
/// 微信公众号接入验签,验签通过后原样返回 echostr。
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/apiwechat/verification")]
|
||||
[HttpHead("/apiwechat/verification")]
|
||||
[Produces("text/plain")]
|
||||
[ApiExplorerSettings(GroupName = "WeChat")]
|
||||
public async Task<ContentResult> WeChatValidateLegacyAsync(
|
||||
public Task<ContentResult> WeChatValidateLegacyAsync(
|
||||
[FromQuery] string? signature,
|
||||
[FromQuery] string? timestamp,
|
||||
[FromQuery] string? nonce,
|
||||
[FromQuery] string? echostr,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.Equals(
|
||||
httpContextAccessor.HttpContext?.Request.Method,
|
||||
HttpMethods.Head,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return WeChatMpCallbackSupport.HeadOk();
|
||||
}
|
||||
|
||||
return await WeChatValidateCoreAsync(signature, timestamp, nonce, echostr, cancellationToken);
|
||||
}
|
||||
CancellationToken cancellationToken = default) =>
|
||||
WeChatValidateCoreAsync(signature, timestamp, nonce, cancellationToken);
|
||||
|
||||
private async Task<ContentResult> WeChatValidateCoreAsync(
|
||||
string? signature,
|
||||
string? timestamp,
|
||||
string? nonce,
|
||||
string? echostr,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var request = httpContextAccessor.HttpContext?.Request;
|
||||
if (request == null)
|
||||
{
|
||||
return WeChatMpCallbackSupport.PlainError();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var options = await WeChatMpConfigProvider.GetAsync(cancellationToken);
|
||||
return WeChatMpCallbackSupport.Validate(options, signature, timestamp, nonce, echostr);
|
||||
return WeChatMpCallbackSupport.Validate(options, request, signature, timestamp, nonce);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -68,7 +62,7 @@ public partial class AuthService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信服务器接入验签。
|
||||
/// 微信服务器接入验签,验签通过后原样返回 echostr。
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/wechat/mp/{appId}")]
|
||||
@ -82,39 +76,11 @@ public partial class AuthService
|
||||
[FromQuery] string? echostr,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = httpContextAccessor.HttpContext?.Request
|
||||
?? throw new BusinessException("无法读取微信验签请求");
|
||||
var options = await WeChatMpConfigProvider.GetAsync(cancellationToken);
|
||||
WeChatMpCallbackSupport.EnsureAppId(options, appId);
|
||||
return WeChatMpCallbackSupport.Validate(options, signature, timestamp, nonce, echostr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号消息/事件回调(POST)
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/apiwechat/verification")]
|
||||
[Consumes("text/xml", "application/xml", "text/plain")]
|
||||
[Produces("application/xml")]
|
||||
[ApiExplorerSettings(GroupName = "WeChat")]
|
||||
public async Task<ContentResult> WeChatMessageLegacyAsync(
|
||||
[FromQuery] string signature,
|
||||
[FromQuery] string timestamp,
|
||||
[FromQuery] string nonce,
|
||||
[FromQuery(Name = "encrypt_type")] string? encryptType,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = await WeChatMpConfigProvider.GetAsync(cancellationToken);
|
||||
var request = httpContextAccessor.HttpContext?.Request
|
||||
?? throw new BusinessException("无法读取微信回调请求");
|
||||
return await WeChatMpCallbackSupport.HandleMessageAsync(
|
||||
options,
|
||||
WeChatEventHandler,
|
||||
request,
|
||||
WeChatLogger,
|
||||
signature,
|
||||
timestamp,
|
||||
nonce,
|
||||
encryptType,
|
||||
cancellationToken);
|
||||
return WeChatMpCallbackSupport.Validate(options, request, signature, timestamp, nonce);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -9,10 +9,10 @@ internal static class WeChatMpCallbackSupport
|
||||
{
|
||||
public static ContentResult Validate(
|
||||
WeChatMpOptions options,
|
||||
HttpRequest request,
|
||||
string? signature,
|
||||
string? timestamp,
|
||||
string? nonce,
|
||||
string? echostr)
|
||||
string? nonce)
|
||||
{
|
||||
// 微信服务器要求 HTTP 200 + 纯文本 echostr,不可抛 BusinessException(会变成 JSON 非 200)
|
||||
if (string.IsNullOrWhiteSpace(options.Token))
|
||||
@ -20,16 +20,21 @@ internal static class WeChatMpCallbackSupport
|
||||
return PlainError();
|
||||
}
|
||||
|
||||
var echostr = request.Query.TryGetValue("echostr", out var echostrValue)
|
||||
? echostrValue.ToString()
|
||||
: string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(signature)
|
||||
|| string.IsNullOrWhiteSpace(timestamp)
|
||||
|| string.IsNullOrWhiteSpace(nonce)
|
||||
|| string.IsNullOrWhiteSpace(echostr))
|
||||
|| string.IsNullOrEmpty(echostr))
|
||||
{
|
||||
return PlainError();
|
||||
}
|
||||
|
||||
var valid = WeChatSignatureHelper.Validate(options.Token, timestamp, nonce, signature);
|
||||
return PlainText(valid ? echostr : "error");
|
||||
// 验签通过后原样返回 echostr,不可包 JSON、不可加引号或换行
|
||||
return valid ? EchoStr(echostr) : PlainError();
|
||||
}
|
||||
|
||||
/// <summary>微信或探活可能发 HEAD,须返回 200(WMS/Spring 默认支持)。</summary>
|
||||
@ -95,6 +100,9 @@ internal static class WeChatMpCallbackSupport
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentResult EchoStr(string echostr) =>
|
||||
new() { Content = echostr, ContentType = "text/plain", StatusCode = StatusCodes.Status200OK };
|
||||
|
||||
private static ContentResult PlainText(string content) =>
|
||||
new() { Content = content, ContentType = "text/plain; charset=utf-8", StatusCode = StatusCodes.Status200OK };
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user