在.NET核心C#控制台应用程序中使用HttpClient进行谷歌社交登录?

在.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

为了学习,我开发了一个ASP.NET核心C#Web API项目。我想为我的Web API集成Google外部登录/身份验证。作为练习,我希望使用HttpClient从控制台应用程序使用google对用户进行身份验证,检索一个令牌,然后在对Web API端点的每个请求中使用该令牌。在控制台应用程序和设置Web API中,我找不到任何有用的教程来解释如何做到这一点。我怎样才能做到这一点?谢谢。

这听起来是个有趣的项目。我以前没有这样做过,但我会这样做

Visual Studio中ASP.NET核心Web应用程序的默认模板已经是控制台应用程序。只需在项目的属性窗口的应用程序选项卡中检查输出类型字段,它将向您显示值控制台

因此,我将从该项目模板开始。在项目创建向导中,将身份验证更改为个人用户帐户,您将获得一个ASP.NET核心Web应用程序,该应用程序几乎可以实现您的外部登录逻辑


然后删除所有与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());
        }

    }
}