using Azure.Core; using IdentityModel; using IdentityModel.Client; using IRaCIS.Core.Application.Service.OAuth; using MassTransit; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NPOI.SS.Formula.Functions; using Org.BouncyCastle.Utilities.Net; using RestSharp; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace IRaCIS.Core.Application.Service { public class OAuthService : ServiceBase { #region authorization_code 原生 PKCE public IResponseOutput TestPCKEOrgin() { // 1. 生成 code_verifier 和 code_challenge string codeVerifier = "QMSBBxTQrpKPscvNNfmaQfmyk5Wd33GZS1FKSo3Shv8w-59vW1iTSlgAznYojkYv2DgR4XhTqySsBnDPq0"; string codeChallenge = PkceUtil.GenerateCodeChallenge(codeVerifier); Console.WriteLine(codeVerifier); string clientId = "aj34vqrpvz8olsbxwtcog"; string redirectUri = "http://localhost:6100/OAuth/TestPKCECallBack"; string state = "123456"; // 构造请求的 URL string authorizationUrl = $"https://logto.test.extimaging.com/oidc/auth" + $"?client_id={clientId}" + $"&redirect_uri={Uri.EscapeDataString(redirectUri)}" + $"&response_type=code" + $"&scope=openid profile email phone" + $"&code_challenge={codeChallenge}" + $"&code_challenge_method=S256" + $"&state={state}"; Console.WriteLine("请将以下 URL 复制到浏览器中,完成登录后获取 code:"); Console.WriteLine(authorizationUrl); return ResponseOutput.Ok(); } [AllowAnonymous] [RoutePattern(HttpMethod = "Get")] public async Task TestPKCECallBackAsync(string code) { var httpClient = new HttpClient(); var disco = await httpClient.GetDiscoveryDocumentAsync("https://logto.test.extimaging.com/oidc"); if (disco.IsError) { Console.WriteLine(disco.Error); } string codeVerifier = "QMSBBxTQrpKPscvNNfmaQfmyk5Wd33GZS1FKSo3Shv8w-59vW1iTSlgAznYojkYv2DgR4XhTqySsBnDPq0"; // OIDC 配置,替换为您的 OIDC 提供者的配置 string tokenEndpoint = "https://logto.test.extimaging.com/oidc/token"; // 替换为实际 token 端点 string clientId = "aj34vqrpvz8olsbxwtcog"; string redirectUri = "http://localhost:6100/OAuth/TestPKCECallBack"; // 替换为前端的回调 URL var baseUrl = "https://logto.test.extimaging.com"; var opts = new RestClientOptions(baseUrl); using var client = new RestClient(opts); //https://blog.logto.io/troubleshoot-invalid-grant-error/ var request = new RestRequest("oidc/token", Method.Post) .AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddParameter("grant_type", "authorization_code") .AddParameter("code", code) .AddParameter("redirect_uri", redirectUri) .AddParameter("client_id", clientId) .AddParameter("code_verifier", codeVerifier); // 使用 PKCE // 发送请求并获取响应 var response = await client.ExecuteAsync(request); if (response.StatusCode == HttpStatusCode.OK) { var tokenResponse = response.Data; Console.WriteLine(tokenResponse.ToJsonStr()); var userInfoRequest = new RestRequest($"oidc/me", Method.Get) .AddHeader("Authorization", $"Bearer {tokenResponse.AccessToken}"); var userResponse = await client.ExecuteAsync(userInfoRequest); Console.WriteLine(userResponse.Content); //结束回话 var endUrl = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(tokenResponse.IdToken, "http://localhost:6100/OAuth/TestPCKEOrgin"); var _endHttpClient = new HttpClient(); var dd = await _endHttpClient.GetAsync(endUrl); } return ResponseOutput.Ok(); } #endregion #region authorization_code OidcClient PKCE [AllowAnonymous] [RoutePattern(HttpMethod = "Get")] public IResponseOutput TestPKCE() { // 1. 生成 code_verifier 和 code_challenge string codeVerifier = "QMSBBxTQrpKPscvNNfmaQfmyk5Wd33GZS1FKSo3Shv8w-59vW1iTSlgAznYojkYv2DgR4XhTqySsBnDPq0"; string codeChallenge = PkceUtil.GenerateCodeChallenge(codeVerifier); Console.WriteLine(codeVerifier); string clientId = "aj34vqrpvz8olsbxwtcog"; string redirectUri = "http://localhost:6100/OAuth/TestOidcClientPKCECallBack"; string state = "123456"; // 构造请求的 URL string authorizationUrl = $"https://logto.test.extimaging.com/oidc/auth" + $"?client_id={clientId}" + $"&redirect_uri={Uri.EscapeDataString(redirectUri)}" + $"&response_type=code" + $"&scope=openid profile email phone" + $"&code_challenge={codeChallenge}" + $"&code_challenge_method=S256" + $"&state={state}"; Console.WriteLine("请将以下 URL 复制到浏览器中,完成登录后获取 code:"); Console.WriteLine(authorizationUrl); return ResponseOutput.Ok(); } [AllowAnonymous] [RoutePattern(HttpMethod = "Get")] public async Task TestOidcClientPKCECallBackAsync(string code) { //使用IdentityModel.OidcClient 测试 var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://logto.test.extimaging.com/oidc"); if (disco.IsError) { Console.WriteLine(disco.Error); } // OIDC 配置,替换为您的 OIDC 提供者的配置 string clientId = "aj34vqrpvz8olsbxwtcog"; string codeVerifier = "QMSBBxTQrpKPscvNNfmaQfmyk5Wd33GZS1FKSo3Shv8w-59vW1iTSlgAznYojkYv2DgR4XhTqySsBnDPq0"; string redirectUri = "http://localhost:6100/OAuth/TestOidcClientPKCECallBack"; // 替换为前端的回调 URL var requestBody = new Dictionary { { "grant_type", "authorization_code" }, { "code", code }, { "redirect_uri", redirectUri }, { "client_id", clientId }, { "code_verifier", codeVerifier } // 使用 PKCE }; var _httpClient = new HttpClient(); var content = new FormUrlEncodedContent(requestBody); // 发出 token 请求 var response = await _httpClient.PostAsync(disco.TokenEndpoint, content); if (response.IsSuccessStatusCode) { var responseBody = await response.Content.ReadAsStringAsync(); // 解析 JSON var jsonObject = JObject.Parse(responseBody); // 格式化并输出 JSON var formattedJson = jsonObject.ToString(Formatting.Indented); Console.WriteLine(formattedJson); var tokenResponse = JsonConvert.DeserializeObject(responseBody); Console.WriteLine(tokenResponse); //结束回话 var parameters = new Parameters(); parameters.Add("clientId", "aj34vqrpvz8olsbxwtcog"); var endUrl = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(tokenResponse.IdToken, "http://localhost:6100/OAuth/TestPCKEOrgin", extra: parameters); Results.Redirect(endUrl); //var _endHttpClient = new HttpClient(); //var dd = await _endHttpClient.GetAsync(endUrl); } else { var errorContent = await response.Content.ReadAsStringAsync(); throw new Exception($"Error: {errorContent}"); } #region 提示必须要Secret //// 准备请求内容 //var tokenRequest = new AuthorizationCodeTokenRequest //{ // Address = disco.TokenEndpoint, // ClientId = clientId, // Code = code, // RedirectUri = redirectUri, // GrantType = "authorization_code", // CodeVerifier = codeVerifier //}; //var tokenResponse = await _httpClient.RequestTokenAsync(tokenRequest); //if (tokenResponse.HttpStatusCode == HttpStatusCode.OK) //{ // var apiClient = new HttpClient(); // apiClient.SetBearerToken(tokenResponse.AccessToken); // var response = await apiClient.GetAsync(disco.UserInfoEndpoint); // if (!response.IsSuccessStatusCode) // { // Console.WriteLine(response.StatusCode); // Console.WriteLine(response.ReasonPhrase); // } // else // { // var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; // Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); // //获取刷新token // var refreshClient = new HttpClient(); // var refreshRequest = new RefreshTokenRequest // { // Address = disco.TokenEndpoint, // ClientId = clientId, // RefreshToken = tokenResponse.RefreshToken, // }; // var refreshResponse = await refreshClient.RequestRefreshTokenAsync(refreshRequest); // if (refreshResponse.IsError) // { // Console.WriteLine($"Error: {refreshResponse.Error}"); // } // else // { // Console.WriteLine("获取刷新token 完成"); // Console.WriteLine("AccessToken:" + refreshResponse.AccessToken); // Console.WriteLine("RefreshToken:" + refreshResponse.RefreshToken); // } // } //} #endregion return ResponseOutput.Ok(); } #endregion #region OIDC authorization_code with client_secret [AllowAnonymous] public IResponseOutput TestOidcClientWithSecret() { string clientId = "tl42rjin7obxtwqqgvkti"; string redirectUri = "http://localhost:6100/OAuth/TestOidcClientCallBack"; string state = "123456"; // 构造请求的 URL string authorizationUrl = $"https://logto.test.extimaging.com/oidc/auth" + $"?client_id={clientId}" + $"&redirect_uri={Uri.EscapeDataString(redirectUri)}" + $"&response_type=code" + $"&scope=openid profile email phone" + $"&state={state}"; Console.WriteLine(authorizationUrl); return ResponseOutput.Ok(); } [AllowAnonymous] [RoutePattern(HttpMethod = "Get")] public async Task TestOidcClientCallBackAsync(string code) { //使用IdentityModel.OidcClient 测试 var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://logto.test.extimaging.com/oidc"); if (disco.IsError) { Console.WriteLine(disco.Error); } // OIDC 配置,替换为您的 OIDC 提供者的配置 string clientId = "tl42rjin7obxtwqqgvkti"; string clientSecret = "Pu9ig4rz44aLlxb0yKUaOiZaFk6Bcu51"; string redirectUri = "http://localhost:6100/OAuth/TestOidcClientCallBack"; // 替换为前端的回调 URL string postLogoutRedirectUri = "http://localhost:6100/OAuth/TestPCKEOrgin"; //退出回话重定向到前端的url // 准备请求内容 var tokenRequest = new AuthorizationCodeTokenRequest { Address = disco.TokenEndpoint, ClientId = clientId, ClientSecret = clientSecret, Code = code, RedirectUri = redirectUri, GrantType = "authorization_code", }; var _httpClient = new HttpClient(); // 发出 token 请求 var tokenResponse = await _httpClient.RequestAuthorizationCodeTokenAsync(tokenRequest); if (tokenResponse.HttpStatusCode == HttpStatusCode.OK) { var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync(disco.UserInfoEndpoint); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); Console.WriteLine(response.ReasonPhrase); } else { var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); //获取刷新token var refreshClient = new HttpClient(); var refreshRequest = new RefreshTokenRequest { Address = disco.TokenEndpoint, ClientId = clientId, ClientSecret = clientSecret, RefreshToken = tokenResponse.RefreshToken, }; var refreshResponse = await refreshClient.RequestRefreshTokenAsync(refreshRequest); if (refreshResponse.IsError) { Console.WriteLine($"Error: {refreshResponse.Error}"); } else { Console.WriteLine("获取刷新token 完成"); Console.WriteLine("AccessToken:" + refreshResponse.AccessToken); Console.WriteLine("RefreshToken:" + refreshResponse.RefreshToken); } } } return ResponseOutput.Ok(); } #endregion #region 客户端凭证 /// /// 测试客户端凭证代码 /// /// public async Task TestClientCredentialsAsync() { #region 使用IdentityModel.OidcClient 测试 // discover endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://logto.test.extimaging.com/oidc"); if (disco.IsError) { Console.WriteLine(disco.Error); } // request token var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "v2mr2ndxwkxz0xpsuc1th", ClientSecret = "yq9jUxl70QoOmwHxJ37h1rDoyJ5iz92Q", Resource = new List() { "https://default.logto.app/api" }, Scope = "all" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); Console.WriteLine(tokenResponse.ErrorDescription); } else { Console.WriteLine(tokenResponse.AccessToken); Console.WriteLine("\n\n"); // call api var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("https://logto.test.extimaging.com/api/applications"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); } } #endregion return ResponseOutput.Ok(); } public async Task TestClientCredentialsOriginAsync() { #region 客户端方式获取logto 里面的信息 { var baseUrl = "https://logto.test.extimaging.com"; var appId = "v2mr2ndxwkxz0xpsuc1th"; var appSecret = "yq9jUxl70QoOmwHxJ37h1rDoyJ5iz92Q"; var apiAddress = "https://default.logto.app/api"; //这里是个坑 var scope = "all"; var opts = new RestClientOptions(baseUrl); using var client = new RestClient(opts); //https://bump.sh/logto/doc/logto-management-api/authentication var request = new RestRequest("oidc/token", Method.Post); request .AddHeader("Content-Type", "application/x-www-form-urlencoded") .AddParameter("grant_type", "client_credentials") .AddParameter("client_id", appId) .AddParameter("client_secret", appSecret) .AddParameter("resource", apiAddress) //注意这里默认值地址和api 地址有区别 .AddParameter("scope", scope); var response = await client.ExecuteAsync(request); if (response.StatusCode == HttpStatusCode.OK) { var tokenResponse = response.Data; Console.WriteLine(tokenResponse.ToJsonStr()); #region 获取应用信息 var applicationRequest = new RestRequest($"/api/applications", Method.Get) .AddHeader("Authorization", $"Bearer {tokenResponse.AccessToken}"); var applicationResponse = await client.ExecuteAsync(applicationRequest); #endregion #region 获取用户信息 //curl \ // -X GET https://[tenant_id].logto.app/api/users/{userId} \ // -H "Authorization: Bearer $ACCESS_TOKEN" var userId = "4fqx4cb3438k"; var userInfoRequest = new RestRequest($"api/users/{userId}", Method.Get) .AddHeader("Authorization", $"Bearer {tokenResponse.AccessToken}"); var userResponse = await client.ExecuteAsync(userInfoRequest); Console.WriteLine(userResponse.Content); #endregion } } #endregion return ResponseOutput.Ok(); } #endregion } public static class PkceUtil { // 生成 code_verifier public static string GenerateCodeVerifier() { var bytes = new byte[64]; using (var random = RandomNumberGenerator.Create()) { random.GetBytes(bytes); return Convert.ToBase64String(bytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } } // 生成 code_challenge public static string GenerateCodeChallenge(string codeVerifier) { using (var sha256 = SHA256.Create()) { var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); return Convert.ToBase64String(bytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } } } }