如何在C#中获得OAuth 2.0身份验证令牌
本文关键字:OAuth 身份验证 令牌 | 更新日期: 2023-09-27 17:58:09
我有这些设置:
- Auth URL(恰好是"https://login.microsoftonline.com/...")如果这有帮助的话
- 访问令牌URL"https://service.endpoint.com/api/oauth2/token"
- ClientId"abc"
- 客户端机密"123"
然后,我需要使用标头中的承载令牌进行get调用。
我可以在Postman中实现这一点,但在试图找出如何在C#中实现它时遇到了困难。我一直在使用RestSharp(但对其他人开放)。这一切看起来都很不透明,当我认为它会很直接的时候:它是一个控制台应用程序,所以我不需要铃声和口哨声。
最终,我希望我的应用程序(通过编程)获得一个令牌,然后将其用于后续调用。我很感激有人给我指一些文件或例子,这些文件或例子清楚地解释了我想要什么。我所遇到的一切都是部分的,或者是针对不同流程的服务。
谢谢。
在Postman中,单击生成代码,然后在产生代码段对话框中,您可以选择不同的编码语言,包括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>");
Rest Client的答案非常完美!(我投了赞成票)
但是,万一你想去";生的";
我用HttpClient做了这个。
"抽象地";你正在做的是
- 创建POST请求
- 带有一个有效载荷体";类型";"x-www-form-urlencoded"的。(请参阅FormUrlEncodedContenthttps://learn.microsoft.com/en-us/dotnet/api/system.net.http.formurlencodedcontent?view=net-5.0,并注意构造函数:https://learn.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进行代码编写。
旁注,newtonsoft引用的是";"弥合差距";在c#命名标准和json元素名称之间。(下面的部分代码示例)。C#属性应该是PascalCase,json元素名称(在这种情况下)是";蛇案";。
[JsonProperty("access_token")]
public string AccessToken { get; set; }
如果您使用的是dotnet核心5.0及更高版本。您可能可以改用System.Text.Json。
但我们来了:
/*
.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月)发现了一篇关于这一切的文章。我将在此处添加一个链接。。。。有时让两个不同的人展示/解释它可以帮助人们学习它。
http://luisquintanilla.me/2017/12/25/client-credentials-authentication-csharp/
这里有一个完整的例子。右键单击解决方案以管理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);
RestResponse 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来实现这一点。使用它的好处是,我们在代码周围获得了一个很好的包装器来获取AccessToken
,并且我们可以开箱即用地获得Token Cache
等附加功能。来自文件:
为什么要使用ADAL.NET?
ADAL.NET V3(适用于.NET的Active Directory身份验证库)使.NET应用程序的开发人员能够获取令牌,以便调用安全的Web API。这些Web API可以是Microsoft Graph或第三方Web API。
以下是代码片段:
// 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;
}
}
您可以使用以下代码来获取承载令牌。
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;
}
此示例通过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();
}
我的客户端正在使用grant_type=Authorization_code
工作流。
我有以下设置:
Auth URL(恰好是一个";https://login.microsoftonline.com/...")如果这有帮助的话。
访问令牌URL:";https://service.endpoint.com/api/oauth2/token"客户端Id:";xyz";客户端机密:";123dfsdf";
然后,我需要使用标头中的承载令牌进行get调用。我尝试了上面所有的代码示例,但无论我将在微软登陆什么-登录到你的帐户"页面为
"'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个调用。
- GET调用授权代码
- 在对Access令牌的调用中附加了上述授权代码的POST调用
我试图在POSTMAN中单独执行上述GET调用,当我这样做时,我会收到微软在线登录页面的提示,当我输入凭据时,我将收到salesforce错误。
如果有人能提供Authorization_code
grand_type
工作流的示例代码,那将是非常有帮助的。。。
清楚:
服务器端生成令牌示例
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;
}
您可以将此令牌用于授权(即在后续请求中)
https://github.com/IdentityModel/IdentityModel为HttpClient
添加了扩展,以使用不同的流获取令牌,文档也很棒。它非常方便,因为您不必考虑如何自己实现它。我不知道是否存在任何正式的MS实现。
我尝试了使用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; }
}
}