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

Posted

技术标签:

【中文标题】如何在 C# 中获取 OAuth 2.0 身份验证令牌【英文标题】:How do I get an OAuth 2.0 authentication token in C# 【发布时间】:2022-03-03 04:36:05 【问题描述】:

我有这些设置:

身份验证 URL(恰好是 "https://login.microsoftonline.com/...") 如果有帮助的话。 访问令牌网址“https://service.endpoint.com/api/oauth2/token” ClientId "abc" 客户密码“123”

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

我可以让它在 Postman 中工作,但在试图弄清楚如何在 C# 中实现它时遇到了困难。我一直在使用 RestSharp(但对其他人开放)。这一切似乎都如此不透明,当我认为这很简单时:它是一个控制台应用程序,所以我不需要花里胡哨。

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

谢谢。

【问题讨论】:

是您的问题得到解决方案,如果是,请在这里分享。 是的,选择的答案如下。其他答案中也有许多不错的选择。 【参考方案1】:

在 Postman 中,单击 Generate Code,然后在 Generate Code Snippets 对话框中,您可以选择不同的编码语言,包括 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);

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

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

【讨论】:

太棒了,完美运行 - 我会责怪菜鸟没有注意到我可以更改生成代码语言。我还发现(发布后)我需要更改安全协议ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 也是必需的(至少在我的情况下),遇到其他明智的状态代码“0”。 你用的是什么命名空间,我对RestClient不熟悉 使用响应共享 access_token 的代码。我找不到它。我也想要 access_token 和 Refresh_token。 救了我的命!非常感谢 在没有先对用户进行身份验证的情况下,您绝不应该允许这样做。【参考方案2】:

Rest Client 的答案是完美的! (我赞成)

但是,以防万一你想“生”

.......

我让它与 HttpClient 一起工作。

“抽象地”你在做什么

    创建 POST 请求。 负载“类型”的主体为“x-www-form-urlencoded”。 (请参阅 FormUrlEncodedContent https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent?view=net-5.0 并注意构造函数:https://docs.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent.-ctor?view=net-5.0) 在 'type' 的有效负载中:x-www-form-urlencoded,您正在输入某些值,例如 grant_type、client_id、client_secret 等。

附注,试着让它在 PostMan 中工作,然后使用下面的代码更容易“编码”。

但是我们开始了,使用 HttpClient 的代码。

.......

/*
.nuget\packages\newtonsoft.json\12.0.1
.nuget\packages\system.net.http\4.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;
    

追加

奖励材料!

如果你得到一个

"远程证书根据验证无效 程序。”

异常......您可以连接处理程序以查看发生了什么(并在必要时进行按摩)

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; 
        

    

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

我最近发现(2020 年 1 月)一篇关于这一切的文章。我将在此处添加一个链接....有时有 2 个不同的人展示/解释它有助于尝试学习它的人。

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

【讨论】:

谢谢!帮助尽量减少 NuGet 包。 使用证书的排列怎么样? 您能否提供您所指的 oauth2 授权类型的链接? alexbilbie.com/guide-to-oauth-2-grants 归根结底,您只是在按摩具有 oauth 风格的 http-request(具有某些标头、有效负载中的某些项目等) 我将这个用于错误的客户端 ID,它应该抛出异常但不会。我使用了 try catch 块,但代码没有打到那里,不知道为什么? 你的 : HttpResponseMessage.StatusCode 属性的值是多少(在我的回答中的这个代码示例中,可能是 tokenResponse.StatusCode )? docs.microsoft.com/en-us/dotnet/api/…【参考方案3】:

这是一个完整的例子。右键解决方案管理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);
                
    

【讨论】:

使用上面的代码得到一个错误:Newtonsoft.Json.dll 中发生“Newtonsoft.Json.JsonReaderException”类型的异常,但未在用户代码中处理附加信息:从 JsonReader 读取 JObject 时出错。路径'',第 0 行,第 0 位置。 @RaviKantSingh 你必须检查你的response。我猜您的请求失败了,因此您得到了错误的响应,JObject 无法理解【参考方案4】:

我使用 ADAL.NET/Microsoft Identity Platform 来实现这一点。使用它的好处是我们可以很好地包装代码以获取AccessToken,并且我们可以获得额外的功能,例如开箱即用的Token Cache。来自documentation:

为什么要使用 ADAL.NET?

ADAL.NET V3(.NET 的 Active Directory 身份验证库)使 .NET 应用程序的开发人员能够获取令牌以调用安全的 Web API。这些 Web API 可以是 Microsoft Graph 或第 3 方 Web API。

这里是sn-p的代码:

    // 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;
       
   

【讨论】:

投反对票的人能否提供投反对票的理由? 不限微软代币吗? 文档说它也应该与第 3 方一起使用。 我没读过。据我了解,唯一的令牌提供者是 Azure AD。 身份验证配置从何而来?【参考方案5】:

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

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;

【讨论】:

【参考方案6】:

这个例子通过 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();
        

【讨论】:

【参考方案7】:

很明显:

服务器端生成令牌示例

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 是我的助手类,其中包含 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;
    

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

我尝试通过这种方式使用 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; 
    

【讨论】:

以上是关于如何在 C# 中获取 OAuth 2.0 身份验证令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JMeter 中进行 OAuth 2.0 身份验证?

处理 OAuth 2.0 身份验证 - 在 ASP.NET MVC 应用程序中获取令牌重定向令牌响应

如何在 Azure 逻辑应用中使用 OAuth 2.0 身份验证?

如何使用基本身份验证而不是 OAuth 2.0 将 Alexa 用户与 3rd 方应用程序相关联

如何在弹出窗口中通过 OAuth 2.0 向 Google 进行身份验证?

如何在 IBM Worklight 6.0 中实现 OAUTH 2.0