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; + } +}