|
@@ -0,0 +1,171 @@
|
|
|
+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;
|
|
|
+
|
|
|
+namespace TEAMModelOS.SDK.Extension
|
|
|
+{
|
|
|
+ public static class CoreTokenExtensions
|
|
|
+ { //var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
|
|
|
+ //var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");
|
|
|
+ private const string issuer = "account.teammodel";
|
|
|
+ //Azure AD 租用戶識別碼(國際、大陸)
|
|
|
+ private static List<string> tenantids = new List<string> { "73a2bcc5-fe99-4566-aa8a-07e7bb287df1", "4807e9cf-87b8-4174-aa5b-e76497d7392b" };
|
|
|
+ private static ConcurrentDictionary<string, KeyVaultSecret> KeyVaultSecrets { get; } = new ConcurrentDictionary<string, KeyVaultSecret>();
|
|
|
+
|
|
|
+
|
|
|
+ #region Access Token
|
|
|
+ /// <summary>
|
|
|
+ /// 產生AccessToken
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="clientID"></param>
|
|
|
+ /// <param name="location">服務位置,Global or China ...</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static async ValueTask<AuthenticationResult> CreateAccessToken(string clientID, string clientSecret, string location)
|
|
|
+ {
|
|
|
+ //從金鑰庫取出秘密,此作法讓所有端直接刷新金鑰,無需傳送秘密,SPA更適用
|
|
|
+ var secret = clientSecret ?? (await GetClientIDSecret(clientID, location)).Value;
|
|
|
+
|
|
|
+ var sts = Enum.Parse<STSEndpoint>(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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 驗證是否為公司Azure發行金鑰,支援大陸國際
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="token"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool ValidateIdToken(string token, string salt)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var handler = new JwtSecurityTokenHandler();
|
|
|
+ var validationParameters = new TokenValidationParameters
|
|
|
+ {
|
|
|
+ RequireExpirationTime = true,
|
|
|
+ ValidateIssuer = false,
|
|
|
+ ValidateAudience = false,
|
|
|
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(salt)),
|
|
|
+ ValidateLifetime = false,
|
|
|
+ //LifetimeValidator = LifetimeValidator,
|
|
|
+ ClockSkew = TimeSpan.Zero
|
|
|
+ };
|
|
|
+ ClaimsPrincipal principal = handler.ValidateToken(token, validationParameters, out SecurityToken securityToken);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ catch(Exception ex)
|
|
|
+ {
|
|
|
+ Trace.WriteLine(ex.Message);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ private static async ValueTask<KeyVaultSecret> GetClientIDSecret(string clientID, string location)
|
|
|
+ { //Azure 金鑰庫處理
|
|
|
+ var s = await Task.Run(() =>
|
|
|
+ {
|
|
|
+ var secret = KeyVaultSecrets.GetOrAdd(clientID, (x) =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var sts = Enum.Parse<CoreServiceClient>(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
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|