如何解决在 .NET Core C# 控制台应用程序中使用 HttpClient 进行 Google 社交登录?
我开发了一个用于学习目的的 ASP.NET Core C# Web API 项目。我想为我的 Web API 集成 Google 外部登录/身份验证。作为练习,我想从使用 HttpClient 的控制台应用程序通过 google 对用户进行身份验证,检索令牌,然后将此令牌用于对我的 Web API 端点的每个请求。我在控制台应用程序和设置 Web API 中都找不到任何有用的教程来解释如何执行此操作。我怎样才能做到这一点?谢谢。
解决方法
谷歌在这个 repo 中有一个很棒的 .net 控制台应用程序示例,看看代码
https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthConsoleApp
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OAuthConsoleApp
{
class Program
{
const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
static async Task<int> Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Required command line arguments: client-id client-secret");
return 1;
}
string clientId = args[0];
string clientSecret = args[1];
Console.WriteLine("+-----------------------+");
Console.WriteLine("| Sign in with Google |");
Console.WriteLine("+-----------------------+");
Console.WriteLine("");
Console.WriteLine("Press any key to sign in...");
Console.ReadKey();
Program p = new Program();
await p.DoOAuthAsync(clientId,clientSecret);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
return 0;
}
// ref http://stackoverflow.com/a/3978040
public static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Loopback,0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
private async Task DoOAuthAsync(string clientId,string clientSecret)
{
// Generates state and PKCE values.
string state = GenerateRandomDataBase64url(32);
string codeVerifier = GenerateRandomDataBase64url(32);
string codeChallenge = Base64UrlEncodeNoPadding(Sha256Ascii(codeVerifier));
const string codeChallengeMethod = "S256";
// Creates a redirect URI using an available port on the loopback address.
string redirectUri = $"http://{IPAddress.Loopback}:{GetRandomUnusedPort()}/";
Log("redirect URI: " + redirectUri);
// Creates an HttpListener to listen for requests on that redirect URI.
var http = new HttpListener();
http.Prefixes.Add(redirectUri);
Log("Listening..");
http.Start();
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",AuthorizationEndpoint,Uri.EscapeDataString(redirectUri),clientId,state,codeChallenge,codeChallengeMethod);
// Opens request in the browser.
Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var context = await http.GetContextAsync();
// Brings the Console to Focus.
BringConsoleToFront();
// Sends an HTTP response to the browser.
var response = context.Response;
string responseString = "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please return to the app.</body></html>";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
await responseOutput.WriteAsync(buffer,buffer.Length);
responseOutput.Close();
http.Stop();
Log("HTTP server stopped.");
// Checks for errors.
string error = context.Request.QueryString.Get("error");
if (error is object)
{
Log($"OAuth authorization error: {error}.");
return;
}
if (context.Request.QueryString.Get("code") is null
|| context.Request.QueryString.Get("state") is null)
{
Log($"Malformed authorization response. {context.Request.QueryString}");
return;
}
// extracts the code
var code = context.Request.QueryString.Get("code");
var incomingState = context.Request.QueryString.Get("state");
// Compares the receieved state to the expected value,to ensure that
// this app made the request which resulted in authorization.
if (incomingState != state)
{
Log($"Received request with invalid state ({incomingState})");
return;
}
Log("Authorization code: " + code);
// Starts the code exchange at the Token Endpoint.
await ExchangeCodeForTokensAsync(code,codeVerifier,redirectUri,clientSecret);
}
async Task ExchangeCodeForTokensAsync(string code,string codeVerifier,string redirectUri,string clientId,string clientSecret)
{
Log("Exchanging code for tokens...");
// builds the request
string tokenRequestUri = "https://www.googleapis.com/oauth2/v4/token";
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code",code,clientSecret
);
// sends the request
HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestUri);
tokenRequest.Method = "POST";
tokenRequest.ContentType = "application/x-www-form-urlencoded";
tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
byte[] tokenRequestBodyBytes = Encoding.ASCII.GetBytes(tokenRequestBody);
tokenRequest.ContentLength = tokenRequestBodyBytes.Length;
using (Stream requestStream = tokenRequest.GetRequestStream())
{
await requestStream.WriteAsync(tokenRequestBodyBytes,tokenRequestBodyBytes.Length);
}
try
{
// gets the response
WebResponse tokenResponse = await tokenRequest.GetResponseAsync();
using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream()))
{
// reads response body
string responseText = await reader.ReadToEndAsync();
Console.WriteLine(responseText);
// converts to dictionary
Dictionary<string,string> tokenEndpointDecoded = JsonConvert.DeserializeObject<Dictionary<string,string>>(responseText);
string accessToken = tokenEndpointDecoded["access_token"];
await RequestUserInfoAsync(accessToken);
}
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
{
var response = ex.Response as HttpWebResponse;
if (response != null)
{
Log("HTTP: " + response.StatusCode);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
// reads response body
string responseText = await reader.ReadToEndAsync();
Log(responseText);
}
}
}
}
}
private async Task RequestUserInfoAsync(string accessToken)
{
Log("Making API Call to Userinfo...");
// builds the request
string userinfoRequestUri = "https://www.googleapis.com/oauth2/v3/userinfo";
// sends the request
HttpWebRequest userinfoRequest = (HttpWebRequest)WebRequest.Create(userinfoRequestUri);
userinfoRequest.Method = "GET";
userinfoRequest.Headers.Add(string.Format("Authorization: Bearer {0}",accessToken));
userinfoRequest.ContentType = "application/x-www-form-urlencoded";
userinfoRequest.Accept = "Accept=text/html,*/*;q=0.8";
// gets the response
WebResponse userinfoResponse = await userinfoRequest.GetResponseAsync();
using (StreamReader userinfoResponseReader = new StreamReader(userinfoResponse.GetResponseStream()))
{
// reads response body
string userinfoResponseText = await userinfoResponseReader.ReadToEndAsync();
Log(userinfoResponseText);
}
}
/// <summary>
/// Appends the given string to the on-screen log,and the debug console.
/// </summary>
/// <param name="output">String to be logged</param>
private void Log(string output)
{
Console.WriteLine(output);
}
/// <summary>
/// Returns URI-safe data with a given input length.
/// </summary>
/// <param name="length">Input length (nb. output will be longer)</param>
/// <returns></returns>
private static string GenerateRandomDataBase64url(uint length)
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] bytes = new byte[length];
rng.GetBytes(bytes);
return Base64UrlEncodeNoPadding(bytes);
}
/// <summary>
/// Returns the SHA256 hash of the input string,which is assumed to be ASCII.
/// </summary>
private static byte[] Sha256Ascii(string text)
{
byte[] bytes = Encoding.ASCII.GetBytes(text);
using (SHA256Managed sha256 = new SHA256Managed())
{
return sha256.ComputeHash(bytes);
}
}
/// <summary>
/// Base64url no-padding encodes the given input buffer.
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
private static string Base64UrlEncodeNoPadding(byte[] buffer)
{
string base64 = Convert.ToBase64String(buffer);
// Converts base64 to base64url.
base64 = base64.Replace("+","-");
base64 = base64.Replace("/","_");
// Strips padding.
base64 = base64.Replace("=","");
return base64;
}
// Hack to bring the Console window to front.
// ref: http://stackoverflow.com/a/12066376
[DllImport("kernel32.dll",ExactSpelling = true)]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public void BringConsoleToFront()
{
SetForegroundWindow(GetConsoleWindow());
}
}
}
,
这听起来像是一个有趣的项目。我以前没有这样做过,但这是我将如何处理它...
Visual Studio 中用于 ASP .NET Core Web 应用程序的默认模板已经是一个控制台应用程序。只需检查项目属性窗口的应用程序选项卡中的输出类型字段,它就会显示值控制台.
所以我会从那个项目模板开始。在项目创建向导中,将身份验证更改为个人用户帐户,您将获得一个 ASP .NET Core Web 应用程序,几乎可以实现您的外部登录逻辑。
然后撕掉与 Web 应用程序相关的所有文件和代码。更改 Startup
类,使其看起来像一个普通控制台项目的 Startup
并繁荣!你应该有一个带有外部登录的准系统控制台应用程序!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。