542 lines
20 KiB
C#
542 lines
20 KiB
C#
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;
|
|
|
|
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<IResponseOutput> 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<LogtoTokenResponse>(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<IResponseOutput> 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<string, string>
|
|
{
|
|
{ "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<LogtoTokenResponse>(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<IResponseOutput> 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 客户端凭证
|
|
|
|
/// <summary>
|
|
/// 测试客户端凭证代码
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<IResponseOutput> 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<string>() { "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<IResponseOutput> 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<LogtoTokenResponse>(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<LogtoUser>(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('/', '_');
|
|
}
|
|
}
|
|
}
|
|
}
|