diff --git a/IRaCIS.Core.Application/Service/MinimalApiService/OAuth/LogtoTokenResponse.cs b/IRaCIS.Core.Application/Service/MinimalApiService/OAuth/LogtoTokenResponse.cs
index 51323d93d..d2f62c7f8 100644
--- a/IRaCIS.Core.Application/Service/MinimalApiService/OAuth/LogtoTokenResponse.cs
+++ b/IRaCIS.Core.Application/Service/MinimalApiService/OAuth/LogtoTokenResponse.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace IRaCIS.Core.Application.Service.OAuth;
@@ -8,30 +9,35 @@ public class LogtoTokenResponse
/// The access token issued by the Logto authorization server.
///
[JsonPropertyName("access_token")]
+ [JsonProperty("access_token")]
public string AccessToken { get; set; } = null!;
///
/// The type of the token issued by the Logto authorization server.
///
[JsonPropertyName("token_type")]
+ [JsonProperty("token_type")]
public string TokenType { get; set; } = null!;
///
/// The lifetime in seconds of the access token.
///
[JsonPropertyName("expires_in")]
+ [JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
///
/// The refresh token, which can be used to obtain new access tokens using the same authorization grant.
///
[JsonPropertyName("refresh_token")]
+ [JsonProperty("refresh_token")]
public string? RefreshToken { get; set; } = null!;
///
/// The ID token, which can be used to verify the identity of the user.
///
[JsonPropertyName("id_token")]
+ [JsonProperty("id_token")]
public string? IdToken { get; set; } = null;
}
diff --git a/IRaCIS.Core.Application/Service/MinimalApiService/OAuthService.cs b/IRaCIS.Core.Application/Service/MinimalApiService/OAuthService.cs
index db9dd5f96..921983ae9 100644
--- a/IRaCIS.Core.Application/Service/MinimalApiService/OAuthService.cs
+++ b/IRaCIS.Core.Application/Service/MinimalApiService/OAuthService.cs
@@ -1,11 +1,20 @@
-using IdentityModel.Client;
+using Azure.Core;
+using IdentityModel;
+using IdentityModel.Client;
using IRaCIS.Core.Application.Service.OAuth;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
+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;
@@ -16,6 +25,360 @@ namespace IRaCIS.Core.Application.Service
{
+
+ #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)
+ {
+ 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);
+ }
+
+ 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);
+
+ }
+ 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
+ // 准备请求内容
+ 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 客户端凭证
+
///
/// 测试客户端凭证代码
///
@@ -66,7 +429,7 @@ namespace IRaCIS.Core.Application.Service
else
{
var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
- Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }));
+ Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }));
}
}
@@ -142,5 +505,37 @@ namespace IRaCIS.Core.Application.Service
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('/', '_');
+ }
+ }
}
}