From 4c441bd6986dcf50457c933ac3d9ece02bf27d24 Mon Sep 17 00:00:00 2001
From: BuknSS <284912933@qq.com>
Date: Tue, 29 Oct 2024 18:59:56 +0800
Subject: [PATCH 1/2] Add support for Alipay's new user identity openid
---
.../AlipayAuthenticationConstants.cs | 11 +++++++++++
.../AlipayAuthenticationHandler.cs | 2 --
.../AlipayAuthenticationOptions.cs | 9 +++++++++
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs
index d6188fc4f..1a2eeca51 100644
--- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs
@@ -25,5 +25,16 @@ public static class Claims
/// The user's gender. F: Female; M: Male.
///
public const string Gender = "urn:alipay:gender";
+
+ ///
+ /// OpenID is the unique identifier of Alipay users in the application dimension.
+ /// See https://opendocs.alipay.com/mini/0ai2i6
+ ///
+ public const string OpenId = "urn:alipay:open_id";
+
+ ///
+ /// Alipay user system internal identifier, will no longer be independently open in the future, and will be replaced by OpenID.
+ ///
+ public const string UserId = "urn:alipay:user_id";
}
}
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
index 513bb3bf5..f01dd8b08 100644
--- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
@@ -134,8 +134,6 @@ protected override async Task CreateTicketAsync(
throw new AuthenticationFailureException($"An error (Code:{code}) occurred while retrieving user information.");
}
- identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, mainElement.GetString("user_id")!, ClaimValueTypes.String, Options.ClaimsIssuer));
-
var principal = new ClaimsPrincipal(identity);
var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, mainElement);
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
index 08677ddd5..5beceb715 100644
--- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
@@ -29,5 +29,14 @@ public AlipayAuthenticationOptions()
ClaimActions.MapJsonKey(Claims.Gender, "gender");
ClaimActions.MapJsonKey(Claims.Nickname, "nick_name");
ClaimActions.MapJsonKey(Claims.Province, "province");
+ ClaimActions.MapJsonKey(Claims.OpenId, "open_id");
+ ClaimActions.MapJsonKey(Claims.UserId, "user_id");
+ ClaimActions.MapCustomJson(System.Security.Claims.ClaimTypes.NameIdentifier, user => user.GetString(NameIdentifierKey));
}
+
+ ///
+ /// Alipay user system internal identifier, which will no longer be open independently in the future and will be replaced by open_id. Currently the default is user_id
+ /// See https://opendocs.alipay.com/mini/0ai2i6?pathHash=13dd5946
+ ///
+ public string NameIdentifierKey { get; set; } = "user_id";
}
From 4da7412f657b399454f453450592747cc89688e2 Mon Sep 17 00:00:00 2001
From: BuknSS <284912933@qq.com>
Date: Wed, 30 Oct 2024 15:27:31 +0800
Subject: [PATCH 2/2] Fix Add support for Alipay certificate signing.
---
.../AlipayAuthenticationHandler.cs | 12 ++++
.../AlipayAuthenticationOptions.cs | 34 +++++++++++-
.../AlipayCertificationUtils.cs | 55 +++++++++++++++++++
3 files changed, 100 insertions(+), 1 deletion(-)
create mode 100644 src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtils.cs
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
index f01dd8b08..735e7ec17 100644
--- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
@@ -59,6 +59,12 @@ protected override async Task ExchangeCodeAsync([NotNull] OA
["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
["version"] = "1.0",
};
+ if (Options.EnableCertSignature)
+ {
+ tokenRequestParameters["app_cert_sn"] = Options.AppCertSN;
+ tokenRequestParameters["alipay_root_cert_sn"] = Options.RootCertSN;
+ }
+
tokenRequestParameters.Add("sign", GetRSA2Signature(tokenRequestParameters));
// PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
@@ -107,6 +113,12 @@ protected override async Task CreateTicketAsync(
["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
["version"] = "1.0",
};
+ if (Options.EnableCertSignature)
+ {
+ parameters["app_cert_sn"] = Options.AppCertSN;
+ parameters["alipay_root_cert_sn"] = Options.RootCertSN;
+ }
+
parameters.Add("sign", GetRSA2Signature(parameters));
var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, parameters);
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
index 5beceb715..dd3a321fc 100644
--- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs
@@ -34,9 +34,41 @@ public AlipayAuthenticationOptions()
ClaimActions.MapCustomJson(System.Security.Claims.ClaimTypes.NameIdentifier, user => user.GetString(NameIdentifierKey));
}
+ public override void Validate()
+ {
+ base.Validate();
+
+ if (!string.IsNullOrEmpty(AppCertSN) && !string.IsNullOrEmpty(RootCertSN))
+ {
+ EnableCertSignature = true;
+ }
+ else if (!string.IsNullOrEmpty(AppCertPath) && !string.IsNullOrEmpty(RootCertPath))
+ {
+ try
+ {
+ AppCertSN = AlipayCertificationUtils.GetCertSN(AppCertPath);
+ RootCertSN = AlipayCertificationUtils.GetRootCertSN(RootCertPath);
+ EnableCertSignature = true;
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"The '{nameof(AppCertPath)}' and '{nameof(RootCertPath)}' options must be set to the correct certificate files.", ex);
+ }
+ }
+ }
+
///
- /// Alipay user system internal identifier, which will no longer be open independently in the future and will be replaced by open_id. Currently the default is user_id
/// See https://opendocs.alipay.com/mini/0ai2i6?pathHash=13dd5946
///
public string NameIdentifierKey { get; set; } = "user_id";
+
+ public string? AppCertPath { get; set; }
+
+ public string? RootCertPath { get; set; }
+
+ public bool EnableCertSignature { get; set; }
+
+ public string AppCertSN { get; set; } = string.Empty;
+
+ public string RootCertSN { get; set; } = string.Empty;
}
diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtils.cs b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtils.cs
new file mode 100644
index 000000000..1788a5670
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtils.cs
@@ -0,0 +1,55 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System.Numerics;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace AspNet.Security.OAuth.Alipay;
+
+///
+/// This class of code refers to the Alipay official SDK code.
+/// See https://github.com/alipay/alipay-sdk-net-all/blob/1b7b73909954b107bddb6476dec68aafcc3f16e9/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs
+/// See https://opendocs.alipay.com/common/056zub?pathHash=91c49771
+///
+internal static class AlipayCertificationUtils
+{
+ internal static string GetCertSN([NotNull] string certFilePath)
+ {
+ using var cert = X509Certificate.CreateFromCertFile(certFilePath);
+ return GetCertSN(cert);
+ }
+
+ internal static string GetCertSN([NotNull] X509Certificate cert)
+ {
+ var issuerDN = cert.Issuer.Replace(", ", ",", StringComparison.InvariantCulture);
+ var input = issuerDN + new BigInteger(cert.GetSerialNumber());
+#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
+ var certSN = Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(input))).ToLowerInvariant();
+#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
+ return certSN;
+ }
+
+ internal static string GetRootCertSN([NotNull] string rootCertPath, [NotNull] string signType = "RSA2")
+ {
+ var certSNs = new List();
+ var certCollection = new X509Certificate2Collection();
+ certCollection.ImportFromPemFile(rootCertPath);
+
+ foreach (var cert in certCollection)
+ {
+ if ((signType.StartsWith("RSA", StringComparison.Ordinal) && cert.SignatureAlgorithm.Value?.StartsWith("1.2.840.113549.1.1", StringComparison.Ordinal) == true) ||
+ (signType.Equals("SM2", StringComparison.Ordinal) && cert.SignatureAlgorithm.Value?.StartsWith("1.2.156.10197.1.501", StringComparison.Ordinal) == true))
+ {
+ certSNs.Add(GetCertSN(cert));
+ }
+ }
+
+ var rootCertSN = string.Join('_', certSNs);
+ return rootCertSN;
+ }
+}