如何在 C# 中获取 OAuth 2.0 身份验证令牌

How do I get an OAuth 2.0 authentication token in C#

我有这些设置:

然后我需要使用 header 中的不记名令牌进行 get 调用。

我可以让它在 Postman 中工作,但在尝试找出如何在 C# 中实现它时遇到了困难。我一直在使用 RestSharp(但对其他人开放)。当我认为它会非常简单时,这一切看起来都很不透明:它是一个控制台应用程序,所以我不需要花里胡哨的东西。

最终,我希望我的应用程序(以编程方式)获取令牌,然后将其用于后续调用。我很感激任何人向我指出文档或示例,这些文档或示例清楚地解释了我所追求的内容。我遇到的一切都是部分的或针对在不同流程上运行的服务。

谢谢。

在 Postman 中,单击 生成代码,然后在 生成代码片段 对话框中,您可以 select 不同的编码语言,包括 C# (RestSharp)。

此外,您应该只需要访问令牌 URL。表单参数为:

grant_type=client_credentials
client_id=abc    
client_secret=123

代码片段:

/* using RestSharp; // https://www.nuget.org/packages/RestSharp/ */

var client = new RestClient("https://service.endpoint.com/api/oauth2/token");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", "grant_type=client_credentials&client_id=abc&client_secret=123", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

然后您可以从响应 body 中获取您的访问令牌。例如,对于 Bearer 令牌类型,您可以将以下 header 添加到后续经过身份验证的请求中:

request.AddHeader("authorization", "Bearer <access_token>");

Rest Client 的回答很完美! (我赞成)

但是,以防万一你想要“原始”

........

我得到这个是为了与 HttpClient 一起工作。

“抽象地”你正在做的是

  1. 正在创建一个 POST 请求。
  2. 有效负载“类型”为 'x-www-form-urlencoded'。 (参见 FormUrlEncodedContent https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent?view=net-5.0 and note the constructor : https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent.-ctor?view=net-5.0
  3. 并且在 'type' 的有效负载中:x-www-form-urlencoded,您要输入某些值,例如 grant_type、client_id、client_secret 等.

旁注,尝试让它在 PostMan 中运行,然后使用下面的代码“编码”起来会更容易。

但是我们开始吧,使用 HttpClient 编写代码。

.......

/*
.nuget\packages\newtonsoft.json.0.1
.nuget\packages\system.net.http.3.4
*/
        
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
    
    
    private static async Task<Token> GetElibilityToken(HttpClient client)
    {
        string baseAddress = @"https://blah.blah.blah.com/oauth2/token";

        string grant_type = "client_credentials";
        string client_id = "myId";
        string client_secret = "shhhhhhhhhhhhhhItsSecret";

        var form = new Dictionary<string, string>
                {
                    {"grant_type", grant_type},
                    {"client_id", client_id},
                    {"client_secret", client_secret},
                };

        HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));
        var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
        Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);
        return tok;
    }
    
    
internal class Token
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }

    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
}       

这是另一个工作示例(基于上面的答案)......还有一些调整。有时令牌服务很挑剔:

    private static async Task<Token> GetATokenToTestMyRestApiUsingHttpClient(HttpClient client)
    {
        /* this code has lots of commented out stuff with different permutations of tweaking the request  */

        /* this is a version of asking for token using HttpClient.  aka, an alternate to using default libraries instead of RestClient */

        OAuthValues oav = GetOAuthValues(); /* object has has simple string properties for TokenUrl, GrantType, ClientId and ClientSecret */

        var form = new Dictionary<string, string>
                {
                    { "grant_type", oav.GrantType },
                    { "client_id", oav.ClientId },
                    { "client_secret", oav.ClientSecret }
                };

        /* now tweak the http client */
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Add("cache-control", "no-cache");

        /* try 1 */
        ////client.DefaultRequestHeaders.Add("content-type", "application/x-www-form-urlencoded");

        /* try 2 */
        ////client.DefaultRequestHeaders            .Accept            .Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));//ACCEPT header

        /* try 3 */
        ////does not compile */client.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

        ////application/x-www-form-urlencoded

        HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, oav.TokenUrl);
        /////req.RequestUri = new Uri(baseAddress);
        
        req.Content = new FormUrlEncodedContent(form);

        ////string jsonPayload = "{\"grant_type\":\"" + oav.GrantType + "\",\"client_id\":\"" + oav.ClientId + "\",\"client_secret\":\"" + oav.ClientSecret + "\"}";
        ////req.Content = new StringContent(jsonPayload,                                                Encoding.UTF8,                                                "application/json");//CONTENT-TYPE header

        req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

        /* now make the request */
        ////HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));
        HttpResponseMessage tokenResponse = await client.SendAsync(req);
        Console.WriteLine(string.Format("HttpResponseMessage.ReasonPhrase='{0}'", tokenResponse.ReasonPhrase));

        if (!tokenResponse.IsSuccessStatusCode)
        {
            throw new HttpRequestException("Call to get Token with HttpClient failed.");
        }

        var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
        Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);

        return tok;
    }

追加

奖金Material!

如果你得到一个

"The remote certificate is invalid according to the validation procedure."

异常......你可以连接一个处理程序来查看发生了什么(必要时进行按摩)

using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
using System.Net;

namespace MyNamespace
{
    public class MyTokenRetrieverWithExtraStuff
    {
        public static async Task<Token> GetElibilityToken()
        {
            using (HttpClientHandler httpClientHandler = new HttpClientHandler())
            {
                httpClientHandler.ServerCertificateCustomValidationCallback = CertificateValidationCallBack;
                using (HttpClient client = new HttpClient(httpClientHandler))
                {
                    return await GetElibilityToken(client);
                }
            }
        }

        private static async Task<Token> GetElibilityToken(HttpClient client)
        {
            // throws certificate error if your cert is wired to localhost // 
            //string baseAddress = @"https://127.0.0.1/someapp/oauth2/token";

            //string baseAddress = @"https://localhost/someapp/oauth2/token";

        string baseAddress = @"https://blah.blah.blah.com/oauth2/token";

        string grant_type = "client_credentials";
        string client_id = "myId";
        string client_secret = "shhhhhhhhhhhhhhItsSecret";

        var form = new Dictionary<string, string>
                {
                    {"grant_type", grant_type},
                    {"client_id", client_id},
                    {"client_secret", client_secret},
                };

            HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));
            var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
            Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);
            return tok;
        }

        private static bool CertificateValidationCallBack(
        object sender,
        System.Security.Cryptography.X509Certificates.X509Certificate certificate,
        System.Security.Cryptography.X509Certificates.X509Chain chain,
        System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            // If the certificate is a valid, signed certificate, return true.
            if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
            {
                return true;
            }

            // If there are errors in the certificate chain, look at each error to determine the cause.
            if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
            {
                if (chain != null && chain.ChainStatus != null)
                {
                    foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
                    {
                        if ((certificate.Subject == certificate.Issuer) &&
                           (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
                        {
                            // Self-signed certificates with an untrusted root are valid. 
                            continue;
                        }
                        else
                        {
                            if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
                            {
                                // If there are any other errors in the certificate chain, the certificate is invalid,
                                // so the method returns false.
                                return false;
                            }
                        }
                    }
                }

                // When processing reaches this line, the only errors in the certificate chain are 
                // untrusted root errors for self-signed certificates. These certificates are valid
                // for default Exchange server installations, so return true.
                return true;
            }


            /* overcome localhost and 127.0.0.1 issue */
            if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
            {
                if (certificate.Subject.Contains("localhost"))
                {
                    HttpRequestMessage castSender = sender as HttpRequestMessage;
                    if (null != castSender)
                    {
                        if (castSender.RequestUri.Host.Contains("127.0.0.1"))
                        {
                            return true;
                        }
                    }
                }
            }

            return false;

        }


        public class Token
        {
            [JsonProperty("access_token")]
            public string AccessToken { get; set; }

            [JsonProperty("token_type")]
            public string TokenType { get; set; }

            [JsonProperty("expires_in")]
            public int ExpiresIn { get; set; }

            [JsonProperty("refresh_token")]
            public string RefreshToken { get; set; }
        }

    }
}

............................

我最近发现 (Jan/2020) 一篇关于这一切的文章。我会在这里添加一个 link....有时有 2 个不同的人 show/explain 它可以帮助尝试学习它的人。

http://luisquintanilla.me/2017/12/25/client-credentials-authentication-csharp/

此示例获取令牌 thout HttpWebRequest

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(pathapi);
        request.Method = "POST";
        string postData = "grant_type=password";
        ASCIIEncoding encoding = new ASCIIEncoding();
        byte[] byte1 = encoding.GetBytes(postData);

        request.ContentType = "application/x-www-form-urlencoded";

        request.ContentLength = byte1.Length;
        Stream newStream = request.GetRequestStream();
        newStream.Write(byte1, 0, byte1.Length);

        HttpWebResponse response = request.GetResponse() as HttpWebResponse;            
        using (Stream responseStream = response.GetResponseStream())
        {
            StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
            getreaderjson = reader.ReadToEnd();
        }

清楚:

服务器端生成令牌示例

private string GenerateToken(string userName)
{
    var someClaims = new Claim[]{
        new Claim(JwtRegisteredClaimNames.UniqueName, userName),
        new Claim(JwtRegisteredClaimNames.Email, GetEmail(userName)),
        new Claim(JwtRegisteredClaimNames.NameId,Guid.NewGuid().ToString())
    };

    SecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Tokenizer.Key));
    var token = new JwtSecurityToken(
        issuer: _settings.Tokenizer.Issuer,
        audience: _settings.Tokenizer.Audience,
        claims: someClaims,
        expires: DateTime.Now.AddHours(_settings.Tokenizer.ExpiryHours),
        signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
}

(注意:Tokenizer 是我的助手 class,它包含 Issuer Audience 等。)

肯定是:

客户端获取用于身份验证的令牌

    public async Task<string> GetToken()
    {
        string token = "";
        var siteSettings = DependencyResolver.Current.GetService<SiteSettings>();

        var client = new HttpClient();
        client.BaseAddress = new Uri(siteSettings.PopularSearchRequest.StaticApiUrl);
        client.DefaultRequestHeaders.Accept.Clear();
        //client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        StatisticUserModel user = new StatisticUserModel()
        {
            Password = siteSettings.PopularSearchRequest.Password,
            Username = siteSettings.PopularSearchRequest.Username
        };

        string jsonUser = JsonConvert.SerializeObject(user, Formatting.Indented);
        var stringContent = new StringContent(jsonUser, Encoding.UTF8, "application/json");
        var response = await client.PostAsync(siteSettings.PopularSearchRequest.StaticApiUrl + "/api/token/new", stringContent);
        token = await response.Content.ReadAsStringAsync();

        return token;
    }

您可以将此令牌用于授权(即在后续请求中)

这是一个完整的例子。右击解决方案管理nuget包,获取Newtonsoft和RestSharp:

using Newtonsoft.Json.Linq;
using RestSharp;
using System;


namespace TestAPI
{
    class Program
    {
        static void Main(string[] args)
        {
            String id = "xxx";
            String secret = "xxx";

            var client = new RestClient("https://xxx.xxx.com/services/api/oauth2/token");
            var request = new RestRequest(Method.POST);
            request.AddHeader("cache-control", "no-cache");
            request.AddHeader("content-type", "application/x-www-form-urlencoded");
            request.AddParameter("application/x-www-form-urlencoded", "grant_type=client_credentials&scope=all&client_id=" + id + "&client_secret=" + secret, ParameterType.RequestBody);
            IRestResponse response = client.Execute(request);

            dynamic resp = JObject.Parse(response.Content);
            String token = resp.access_token;            

            client = new RestClient("https://xxx.xxx.com/services/api/x/users/v1/employees");
            request = new RestRequest(Method.GET);
            request.AddHeader("authorization", "Bearer " + token);
            request.AddHeader("cache-control", "no-cache");
            response = client.Execute(request);
        }        
    }
}

我用了ADAL.NET/Microsoft Identity Platform to achieve this. The advantage of using it was that we get a nice wrapper around the code to acquire AccessToken and we get additional features like Token Cache out-of-the-box. From the documentation:

Why use ADAL.NET ?

ADAL.NET V3 (Active Directory Authentication Library for .NET) enables developers of .NET applications to acquire tokens in order to call secured Web APIs. These Web APIs can be the Microsoft Graph, or 3rd party Web APIs.

这是代码片段:

    // Import Nuget package: Microsoft.Identity.Client
    public class AuthenticationService
    {
         private readonly List<string> _scopes;
         private readonly IConfidentialClientApplication _app;

        public AuthenticationService(AuthenticationConfiguration authentication)
        {

             _app = ConfidentialClientApplicationBuilder
                         .Create(authentication.ClientId)
                         .WithClientSecret(authentication.ClientSecret)
                         .WithAuthority(authentication.Authority)
                         .Build();

           _scopes = new List<string> {$"{authentication.Audience}/.default"};
       }

       public async Task<string> GetAccessToken()
       {
           var authenticationResult = await _app.AcquireTokenForClient(_scopes) 
                                                .ExecuteAsync();
           return authenticationResult.AccessToken;
       }
   }

https://github.com/IdentityModel/IdentityModelHttpClient 添加扩展以使用不同的流程获取令牌,文档也很棒。它非常方便,因为您不必自己考虑如何实现它。我不知道是否存在任何官方 MS 实现。

您可以使用以下代码获取不记名令牌。

private string GetBearerToken()
{
    var client = new RestClient("https://service.endpoint.com");
    client.Authenticator = new HttpBasicAuthenticator("abc", "123");
    var request = new RestRequest("api/oauth2/token", Method.POST);
    request.AddHeader("content-type", "application/json");
    request.AddParameter("application/json", "{ \"grant_type\":\"client_credentials\" }", 
    ParameterType.RequestBody);
    var responseJson = _client.Execute(request).Content;
    var token = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseJson)["access_token"].ToString();
    if(token.Length == 0)
    {
        throw new AuthenticationException("API authentication failed.");
    }
    return token;
}

我尝试通过这种方式使用 c# 获取 OAuth 2.0 身份验证令牌

namespace ConsoleApp2
{

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(GetToken());
            Console.Read();
        }

        /// <summary>
        /// Get access token from api
        /// </summary>
        /// <returns></returns>
        private static string GetToken()
        {
            string wClientId = "#######";
            string wClientSecretKey = "*********************";
            string wAccessToken;

//--------------------------- Approch-1 to get token using HttpClient -------------------------------------------------------------------------------------
            HttpResponseMessage responseMessage;
            using (HttpClient client = new HttpClient())
            {
                HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://localhost:1001/oauth/token");
                HttpContent httpContent = new FormUrlEncodedContent(
                        new[]
                        {
                                        new KeyValuePair<string, string>("grant_type", "client_credentials"),
                        });
                tokenRequest.Content = httpContent;
                tokenRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(wClientId + ":" + wClientSecretKey)));
                responseMessage =  client.SendAsync(tokenRequest).Result;
            }
            string ResponseJSON=   responseMessage.Content.ReadAsStringAsync().Result;


//--------------------------- Approch-2 to get token using HttpWebRequest and deserialize json object into ResponseModel class -------------------------------------------------------------------------------------


            byte[] byte1 = Encoding.ASCII.GetBytes("grant_type=client_credentials");

            HttpWebRequest oRequest = WebRequest.Create("https://localhost:1001/oauth/token") as HttpWebRequest;
            oRequest.Accept = "application/json";
            oRequest.Method = "POST";
            oRequest.ContentType = "application/x-www-form-urlencoded";
            oRequest.ContentLength = byte1.Length;
            oRequest.KeepAlive = false;
            oRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(wClientId + ":" + wClientSecretKey)));
            Stream newStream = oRequest.GetRequestStream();
            newStream.Write(byte1, 0, byte1.Length);

            WebResponse oResponse = oRequest.GetResponse();

            using (var reader = new StreamReader(oResponse.GetResponseStream(), Encoding.UTF8))
            {
                var oJsonReponse = reader.ReadToEnd();
                ResponseModel oModel = JsonConvert.DeserializeObject<ResponseModel>(oJsonReponse);
                wAccessToken = oModel.access_token;
            }

            return wAccessToken;
        }
    }
  
  //----------------------------------------------------------------------------------------------------------------------------------------------------
  //---------------------------------- Response Class---------------------------------------------------------------------------------------
  //----------------------------------------------------------------------------------------------------------------------------------------------------

    /// <summary>
    /// De-serialize Web response Object into model class to read  
    /// </summary>
    public class ResponseModel
    {
        public string scope { get; set; }
        public string token_type { get; set; }
        public string expires_in { get; set; }
        public string refresh_token { get; set; }
        public string access_token { get; set; }
    }
}

我的客户正在使用 grant_type=Authorization_code 工作流程。

我有以下设置:

Auth URL(恰好是“https://login.microsoftonline.com/...”)如果有帮助的话。

访问令牌URL:“https://service.endpoint.com/api/oauth2/token” 客户编号:“xyz” 客户端密码:“123dfsdf”

然后我需要使用 header 中的不记名令牌进行 get 调用。我尝试了以上所有代码示例,但无论我将登陆 Microsoft - 登录您的帐户”页面为

"\r\n\r\n<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->\r\n<!DOCTYPE html>\r\n<html dir=\"ltr\" class=\"\" lang=\"en\">\r\n<head>\r\n    <title>Sign in to your account</title>\r\n "

我可以在 Postman 上执行,我观察到控制台中有 2 个调用。

  1. GET 请求授权码
  2. POST 使用附加在访问令牌的调用中的上述授权代码进行调用。

我试图在 POSTMAN 中单独执行上述 GET 调用,当我这样做时,系统会提示我使用 microsoftonline 登录页面,当我输入我的凭据时,我会收到 salesforce 错误。

如果任何人都可以提供示例代码或 Authorization_code grand_type 工作流程的示例那将是非常大的帮助...