在.NET核心C#控制台应用程序中使用HttpClient进行谷歌社交登录?
为了学习,我开发了一个ASP.NET核心C#Web API项目。我想为我的Web API集成Google外部登录/身份验证。作为练习,我希望使用HttpClient从控制台应用程序使用google对用户进行身份验证,检索一个令牌,然后在对Web API端点的每个请求中使用该令牌。在控制台应用程序和设置Web API中,我找不到任何有用的教程来解释如何做到这一点。我怎样才能做到这一点?谢谢。这听起来是个有趣的项目。我以前没有这样做过,但我会这样做 Visual Studio中ASP.NET核心Web应用程序的默认模板已经是控制台应用程序。只需在项目的属性窗口的应用程序选项卡中检查输出类型字段,它将向您显示值控制台 因此,我将从该项目模板开始。在项目创建向导中,将身份验证更改为个人用户帐户,您将获得一个ASP.NET核心Web应用程序,该应用程序几乎可以实现您的外部登录逻辑在.NET核心C#控制台应用程序中使用HttpClient进行谷歌社交登录?,c#,dotnet-httpclient,google-login,C#,Dotnet Httpclient,Google Login,为了学习,我开发了一个ASP.NET核心C#Web API项目。我想为我的Web API集成Google外部登录/身份验证。作为练习,我希望使用HttpClient从控制台应用程序使用google对用户进行身份验证,检索一个令牌,然后在对Web API端点的每个请求中使用该令牌。在控制台应用程序和设置Web API中,我找不到任何有用的教程来解释如何做到这一点。我怎样才能做到这一点?谢谢。这听起来是个有趣的项目。我以前没有这样做过,但我会这样做 Visual Studio中ASP.NET核心We
然后删除所有与web应用程序相关的文件和代码。更改
Startup
类,使其看起来像正常控制台项目和boom的Startup
!你应该有一个带有外部登录的基本控制台应用程序 本回购协议中有一个google for.net控制台应用程序的很好示例,请查看代码
使用Newtonsoft.Json;
使用制度;
使用System.Collections.Generic;
使用系统诊断;
使用System.IO;
Net系统;
使用System.Net.Sockets;
使用System.Runtime.InteropServices;
使用System.Security.Cryptography;
使用系统文本;
使用System.Threading.Tasks;
名称空间OAuthConsoleApp
{
班级计划
{
常量字符串授权端点=”https://accounts.google.com/o/oauth2/v2/auth";
静态异步任务主(字符串[]args)
{
如果(参数长度!=2)
{
WriteLine(“必需的命令行参数:client id client secret”);
返回1;
}
字符串clientId=args[0];
字符串clientSecret=args[1];
Console.WriteLine(“+--------------------------+”);
Console.WriteLine(“使用Google登录”);
Console.WriteLine(“+--------------------------+”);
控制台。写线(“”);
Console.WriteLine(“按任意键登录…”);
Console.ReadKey();
程序p=新程序();
等待p.dooauthsync(clientId,clientSecret);
Console.WriteLine(“按任意键退出…”);
Console.ReadKey();
返回0;
}
//参考号http://stackoverflow.com/a/3978040
公共静态int GetRandomUnusedPort()
{
var listener=new TcpListener(IPAddress.Loopback,0);
listener.Start();
var port=((IPEndPoint)listener.LocalEndpoint).port;
listener.Stop();
返回端口;
}
私有异步任务DoOAuthAsync(字符串clientId、字符串clientSecret)
{
//生成state和PKCE值。
字符串状态=GeneratorDomainDatabase64URL(32);
string codeVerifier=GeneratorDomainDatabase64URL(32);
字符串codecchallenge=Base64UrlEncodeNoPadding(SHA256ASCI(codeVerifier));
常量字符串编解码器挑战方法=“S256”;
//使用环回地址上的可用端口创建重定向URI。
字符串重定向URI=$“http://{IPAddress.Loopback}:{getrandomunsedport()}/”;
日志(“重定向URI:+redirectUri”);
//创建HttpListener以侦听该重定向URI上的请求。
var http=newhttplistener();
http.Prefixes.Add(重定向URI);
日志(“倾听”);
http.Start();
//创建OAuth 2.0授权请求。
string authorizationRequest=string.Format(“{0}?响应类型=code&scope=openid%20profile&redirect_-uri={1}&客户端_-id={2}&状态={3}&代码_-challenge={4}&代码_-challenge_-method={5}”,
授权端点,
Uri.EscapeDataString(重定向Uri),
clientId,
国家,,
挑战,
编码挑战法);
//在浏览器中打开请求。
流程启动(授权请求);
//等待OAuth授权响应。
var context=await http.GetContextAsync();
//使控制台成为焦点。
布林康索尔托弗前部();
//向浏览器发送HTTP响应。
var-response=context.response;
string responseString=“请返回应用程序。”;
byte[]buffer=Encoding.UTF8.GetBytes(responseString);
response.ContentLength64=buffer.Length;
var responseOutput=response.OutputStream;
wait responseOutput.WriteAsync(buffer,0,buffer.Length);
responseOutput.Close();
http.Stop();
日志(“HTTP服务器已停止”);
//检查错误。
字符串错误=context.Request.QueryString.Get(“错误”);
if(错误为对象)
{
日志($“OAuth授权错误:{error}”);
返回;
}
if(context.Request.QueryString.Get(“代码”)为null
||context.Request.QueryString.Get(“state”)为空)
{
日志($“格式错误的授权响应{context.Request.QueryString}”);
返回;
}
//提取代码
var code=context.Request.QueryString.Get(“代码”);
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, 0, 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, clientId, 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,
Uri.EscapeDataString(redirectUri),
clientId,
codeVerifier,
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, 0, 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,application/xhtml+xml,application/xml;q=0.9,*/*;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());
}
}
}