using System; using System.Collections.Generic; using System.Text; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; using Microsoft.Identity.Client; using System.ComponentModel; using System.Threading.Tasks; using Azure.Security.KeyVault.Secrets; using Azure.Core; using Azure.Identity; using System.Net.Http; using System.Collections.Concurrent; using System.Diagnostics; using Newtonsoft.Json; using System.Net; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Configuration; using System.Configuration; using DocumentFormat.OpenXml.Spreadsheet; using System.Text.Json; using System.Security.Cryptography; using System.Reflection; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols; using static Google.Protobuf.Reflection.SourceCodeInfo.Types; namespace TEAMModelOS.SDK.Extension { public static class CoreTokenExtensions { //var issuer = Configuration.GetValue("JwtSettings:Issuer"); //var signKey = Configuration.GetValue("JwtSettings:SignKey"); private const string issuer = "account.teammodel"; //Azure AD 租用戶識別碼(國際、大陸) private static List tenantids = new List { "73a2bcc5-fe99-4566-aa8a-07e7bb287df1", "4807e9cf-87b8-4174-aa5b-e76497d7392b" }; private static ConcurrentDictionary KeyVaultSecrets { get; } = new ConcurrentDictionary(); #region Access Token /// /// 產生AccessToken /// /// /// 服務位置,Global or China ... /// public static async ValueTask CreateAccessToken(string clientID, string clientSecret, string location) { //從金鑰庫取出秘密,此作法讓所有端直接刷新金鑰,無需傳送秘密,SPA更適用 var secret = clientSecret ?? (await GetClientIDSecret(clientID, location)).Value; var sts = Enum.Parse(location, true); IConfidentialClientApplication app; app = ConfidentialClientApplicationBuilder.Create(clientID) .WithClientSecret(secret) .WithAuthority(new Uri(sts.GetDescriptionText())) .Build(); var scope = ((STSScope)sts).GetDescriptionText(); var result = await app.AcquireTokenForClient(new[] { scope }).ExecuteAsync(); return result; } //https://learn.microsoft.com/zh-cn/entra/identity-platform/access-tokens 验证的相关文档 public static async Task Validate(string jwtTokenToValidate, string location,string tenantId, IConfiguration configuration) { // var tenantId = "4807e9cf-87b8-4174-aa5b-e76497d7392b"; var OpenidConfiguration = Enum.Parse(location, true); var openIdConnectWellKnownConfigUri = new Uri(OpenidConfiguration.GetDescriptionText()); //With the Input token to be validated... //With the above information we can validate all key aspects of the Jwt Token... try { var openIdConfigManager = new ConfigurationManager( openIdConnectWellKnownConfigUri.ToString(), new OpenIdConnectConfigurationRetriever() ); OpenIdConnectConfiguration openIdConfig = await openIdConfigManager.GetConfigurationAsync().ConfigureAwait(false); TokenValidationParameters validationParams = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateAudience = true, ValidateIssuer = true, ValidateLifetime = false, ValidateTokenReplay = true, RequireExpirationTime = true, RequireAudience= true, RequireSignedTokens= true, //Valid values for Validation of the JWT... ValidAudience = configuration.GetValue("Option:Audience"), ValidIssuer = openIdConfig.Issuer.Replace("{tenantid}", tenantId), //Set the Azure AD SigningKeys for Validation! IssuerSigningKeys = openIdConfig.SigningKeys, }; var jwtTokenHandler = new JwtSecurityTokenHandler(); jwtTokenHandler.ValidateToken(jwtTokenToValidate, validationParams, out SecurityToken validToken); return validToken as JwtSecurityToken ?? throw new SecurityTokenValidationException("Unexpected failure while parsing and validating the the JWT token specified."); } catch (Exception exc) { //Handle the Token Validation Exception (one of many types may occur)... return null; } } /// /// 驗證是否為公司Azure發行金鑰,支援大陸國際 /// /// /// public static bool ValidateAccessToken(JwtSecurityToken token) { try { if (token.Payload.TryGetValue("tid", out var value) && value is string tokenTenantId) { return tenantids.Contains(tokenTenantId); } return false; } catch (Exception) { return false; } } #endregion private static async ValueTask GetClientIDSecret(string clientID, string location) { //Azure 金鑰庫處理 var s = await Task.Run(() => { var secret = KeyVaultSecrets.GetOrAdd(clientID, (x) => { try { var sts = Enum.Parse(location, true); var scrtetstring = sts.GetDescriptionText().Split(","); //TODO 之後驗證端點用KnownAuthorityHosts取代,此SDK版本無支援 var secret = new ClientSecretCredential(scrtetstring[0], scrtetstring[1], scrtetstring[2], new TokenCredentialOptions() { AuthorityHost = new Uri(scrtetstring[3]) }); var client = new SecretClient(new Uri(((KeyVaultEndpoint)sts).GetDescriptionText()), secret); var clientSecret = client.GetSecretAsync(clientID).ConfigureAwait(false); return clientSecret.GetAwaiter().GetResult(); } catch { return null; } }); return secret; }); return s; } public static bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) { return true; //if (expires != null) //{ // if (DateTime.UtcNow < expires) // { // return true; // } //} //return false; } private enum STSEndpoint { [Description("https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b")] China, [Description("https://login.microsoftonline.com/73a2bcc5-fe99-4566-aa8a-07e7bb287df1")] Global } private enum STSScope { [Description("api://72643704-b2e7-4b26-b881-bd5865e7a7a5/.default")] China, [Description("api://8768b06f-c5c5-4b0c-abfb-d7ded354626d/.default")] Global } private enum KeyVaultEndpoint { [Description("https://corekeyvaultcn.vault.azure.cn/")] China, [Description("https://corekeyvaultjp.vault.azure.net/")] Global } private enum CoreServiceClient { [Description("4807e9cf-87b8-4174-aa5b-e76497d7392b,72643704-b2e7-4b26-b881-bd5865e7a7a5,tRYbDXtotEOe2Bbmo=[3h9Hbu_Trt:c6,https://login.partner.microsoftonline.cn")] China, [Description("73a2bcc5-fe99-4566-aa8a-07e7bb287df1,8768b06f-c5c5-4b0c-abfb-d7ded354626d,7=O./yws0L89WcEsece:9/4deJHP4E=F,https://login.microsoftonline.com/")] Global } private enum STSJwtKeys { [Description("https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/discovery/v2.0/keys")] China, [Description("https://login.microsoftonline.com/73a2bcc5-fe99-4566-aa8a-07e7bb287df1/discovery/v2.0/keys")] Global } private enum STSOpenidConfiguration { [Description("https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0/.well-known/openid-configuration")] China, [Description("https://login.microsoftonline.com/73a2bcc5-fe99-4566-aa8a-07e7bb287df1/v2.0/.well-known/openid-configuration")] Global } public class MSADJwtKeys { public string kty { get;set; } public string use { get; set; } public string kid { get; set; } public string x5t { get; set; } public string n { get; set; } public string e { get; set; } public List x5c { get; set; } public string issuer { get; set; } } } }