c# REST API 中本机/桌面应用程序的 Keycloak 无法正常工作或者我遗漏了什么?
Keycloack for native /desktop application in c# REST APIs, not working properly or am I missing something?
只有当我已经打开浏览器并且已经登录时,我才能通过 keycloak 端点获取授权码和访问令牌,否则代码无法获取授权码。
所以考虑到我在应用程序启动时实现了这个逻辑(它不是确定的),我必须 运行 它 2 次才能获取代码,然后是令牌。
我只使用 rest API,因为我找不到可理解的用于桌面/本机应用程序的 keycloak 库。
这是我的代码:
public static async Task<string> GetTokenAsync()
{
string redirectURI = string.Format("http://{0}:{1}/", "localhost", GetRandomUnusedPort());
string RedirectURIWithoutLastSlash = redirectURI.TrimEnd('/');
string code = await GetCodeForAuthentication(redirectURI, RedirectURIWithoutLastSlash);
UnityWebRequest request = new UnityWebRequest();
request.url = $"https://tokenEndpoint.../protocol/openid-connect/token";
request.method = UnityWebRequest.kHttpVerbPOST;
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
string postData = "";
Dictionary<string, string> postParameters = new Dictionary<string, string>() /
{
{"grant_type", "authorization_code"},
{"code", code},
{"client_id", "myID"},
{"client_secret", "mySECRET"},
{"redirect_uri", RedirectURIWithoutLastSlash }
};
foreach (string key in postParameters.Keys)
postData += UnityWebRequest.EscapeURL(key) + "=" + UnityWebRequest.EscapeURL(postParameters[key]) + "&";
byte[] data = Encoding.ASCII.GetBytes(postData);
request.uploadHandler = new UploadHandlerRaw(data) ;
request.timeout = 60;
request.SendWebRequest();
while (!request.isDone)
{
Debug.Log(request.downloadProgress);
}
Debug.Log("text by server is: " + request.downloadHandler.text);
TokenClass token = JsonUtility.FromJson<TokenClass>(request.downloadHandler.text);
access_token = token.access_token;
tokenInfo = token;
Debug.Log("access token is: " + access_token);
return access_token;
}
private static async Task<string> GetCodeForAuthentication(string redirectURI, string RedirectURIWithoutLastSlash)
{
string code = null;
HttpListener http = new HttpListener();
http.Prefixes.Add(redirectURI);
http.Start();
string authorizationRequest = $"https://authCode Endpoint.../protocol/openid-connect/auth?client_id=myID&response_type=code&response_mode=query&scope=profile&redirect_uri={RedirectURIWithoutLastSlash}";
// Opens request in the browser.
Process web_Login_Process = new Process();
web_Login_Process.StartInfo.FileName = GetSystemDefaultBrowser();
web_Login_Process.StartInfo.Arguments = authorizationRequest;
web_Login_Process.Start();
try
{
var context = await http.GetContextAsync();
var response = context.Response;
var html = "<html><head><meta http-equiv='refresh' content='10;url=https://localhost'></head><body>Please return to the Digital Twin.</body></html>";
string responseString = string.Format(html);
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
responseOutput.Close();
http.Stop();
Console.WriteLine("HTTP server stopped.");
});
// Checks for errors.
if (context.Request.QueryString.Get("error") != null)
{
string debug = context.Request.QueryString.Get("error");
}
if (context.Request.QueryString.Get("code") == null
|| context.Request.QueryString.Get("state") == null)
{
var debug = context.Request.QueryString; //no keys, Lenght is equal to 0
var debug3 = context.Request.RawUrl; // favicon.ico is the result
}
// extracts the code
code = context.Request.QueryString.Get("code");
string tryCode = context.Request.QueryString["code"];
Debug.Log("code is: " + code);
}
catch (Exception ex)
{
string g = ex.Message;
}
return code;
}
如有任何帮助,我们将不胜感激。
有一个 C# Sample 您可以遵循桌面流程并从中借鉴一些想法。请注意,控制台应用程序的 OAuth 流程与桌面应用程序相同。
您应该使用 PKCE 来确保桌面应用程序的安全性,上面的库将为您实现这一点
要与正确的 OAuth 行为进行比较,请参阅我的这些资源:
只有当我已经打开浏览器并且已经登录时,我才能通过 keycloak 端点获取授权码和访问令牌,否则代码无法获取授权码。
所以考虑到我在应用程序启动时实现了这个逻辑(它不是确定的),我必须 运行 它 2 次才能获取代码,然后是令牌。
我只使用 rest API,因为我找不到可理解的用于桌面/本机应用程序的 keycloak 库。
这是我的代码:
public static async Task<string> GetTokenAsync()
{
string redirectURI = string.Format("http://{0}:{1}/", "localhost", GetRandomUnusedPort());
string RedirectURIWithoutLastSlash = redirectURI.TrimEnd('/');
string code = await GetCodeForAuthentication(redirectURI, RedirectURIWithoutLastSlash);
UnityWebRequest request = new UnityWebRequest();
request.url = $"https://tokenEndpoint.../protocol/openid-connect/token";
request.method = UnityWebRequest.kHttpVerbPOST;
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
string postData = "";
Dictionary<string, string> postParameters = new Dictionary<string, string>() /
{
{"grant_type", "authorization_code"},
{"code", code},
{"client_id", "myID"},
{"client_secret", "mySECRET"},
{"redirect_uri", RedirectURIWithoutLastSlash }
};
foreach (string key in postParameters.Keys)
postData += UnityWebRequest.EscapeURL(key) + "=" + UnityWebRequest.EscapeURL(postParameters[key]) + "&";
byte[] data = Encoding.ASCII.GetBytes(postData);
request.uploadHandler = new UploadHandlerRaw(data) ;
request.timeout = 60;
request.SendWebRequest();
while (!request.isDone)
{
Debug.Log(request.downloadProgress);
}
Debug.Log("text by server is: " + request.downloadHandler.text);
TokenClass token = JsonUtility.FromJson<TokenClass>(request.downloadHandler.text);
access_token = token.access_token;
tokenInfo = token;
Debug.Log("access token is: " + access_token);
return access_token;
}
private static async Task<string> GetCodeForAuthentication(string redirectURI, string RedirectURIWithoutLastSlash)
{
string code = null;
HttpListener http = new HttpListener();
http.Prefixes.Add(redirectURI);
http.Start();
string authorizationRequest = $"https://authCode Endpoint.../protocol/openid-connect/auth?client_id=myID&response_type=code&response_mode=query&scope=profile&redirect_uri={RedirectURIWithoutLastSlash}";
// Opens request in the browser.
Process web_Login_Process = new Process();
web_Login_Process.StartInfo.FileName = GetSystemDefaultBrowser();
web_Login_Process.StartInfo.Arguments = authorizationRequest;
web_Login_Process.Start();
try
{
var context = await http.GetContextAsync();
var response = context.Response;
var html = "<html><head><meta http-equiv='refresh' content='10;url=https://localhost'></head><body>Please return to the Digital Twin.</body></html>";
string responseString = string.Format(html);
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
responseOutput.Close();
http.Stop();
Console.WriteLine("HTTP server stopped.");
});
// Checks for errors.
if (context.Request.QueryString.Get("error") != null)
{
string debug = context.Request.QueryString.Get("error");
}
if (context.Request.QueryString.Get("code") == null
|| context.Request.QueryString.Get("state") == null)
{
var debug = context.Request.QueryString; //no keys, Lenght is equal to 0
var debug3 = context.Request.RawUrl; // favicon.ico is the result
}
// extracts the code
code = context.Request.QueryString.Get("code");
string tryCode = context.Request.QueryString["code"];
Debug.Log("code is: " + code);
}
catch (Exception ex)
{
string g = ex.Message;
}
return code;
}
如有任何帮助,我们将不胜感激。
有一个 C# Sample 您可以遵循桌面流程并从中借鉴一些想法。请注意,控制台应用程序的 OAuth 流程与桌面应用程序相同。
您应该使用 PKCE 来确保桌面应用程序的安全性,上面的库将为您实现这一点
要与正确的 OAuth 行为进行比较,请参阅我的这些资源: