我正在尝试使用Client credential grant
(https://discord.com/developers/docs/topics/oauth2#client-credentials-grant)连接到Discord的开放身份验证端点
Discord要求将scope
作为urlencoded字符串发送:identify%20email%20guilds
默认情况下,C# HttpClient似乎将空格转换为+
,而不是%20
。
以下代码
var scopeasStr = string.Join(" ", opts.Scopes);
//scopeasStr = HttpUtility.UrlEncode(scopeasStr);
//scopeasStr = Uri.EscapeDataString(scopeasStr);
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", opts.GrantType));
nvc.Add(new KeyValuePair<string, string>("scope", scopeasStr));
var content = new FormUrlEncodedContent(nvc);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{apiUrl}/oauth2/token");
requestMessage.Content = content;
var response = await externalHttpClient.SendAsync(requestMessage);
生成以下请求
POST https://discord.com/api/v10/oauth2/token HTTP/1.1
Host: discord.com
Authorization: Basic VerySecret
Content-Type: application/x-www-form-urlencoded
Content-Length: 57
grant_type=client_credentials&scope=identify+guilds+email
返回一个400 Bad Request
{"error": "invalid_scope", "error_description": "The requested scope is invalid, unknown, or malformed."}
我试着用scopeasStr = Uri.EscapeDataString(scopeasStr);
来编码这个值,但是%20
被httpClient
编码成了%2520
POST https://discord.com/api/v10/oauth2/token HTTP/1.1
Host: discord.com
Authorization: Basic VerySecret
Content-Type: application/x-www-form-urlencoded
Content-Length: 65
grant_type=client_credentials&scope=identify%2520guilds%2520email
- 当只发送一个作用域时,请求有效。所以肯定是作用域中的空间导致了这个问题。我已经向Discord支持部门确认了他们只接受
%20
来分隔作用域。 - 我应该如何使用HttpClient正确地对此进行编码?
完整C#代码:
public static async Task Authenticate(this HttpClient client, AuthenticateOptions opts, bool forceNew = false)
{
if (client.DefaultRequestHeaders.Authorization == null || forceNew)
{
var externalHttpClient = new HttpClient();
var apiUrl = opts.EndPointUrl;
var clientId = opts.ClientId;
var secret = opts.ClientSecret;
var scopeasStr = string.Join(" ", opts.Scopes);
//scopeasStr = HttpUtility.UrlEncode(scopeasStr);
//scopeasStr = Uri.EscapeDataString(scopeasStr);
externalHttpClient.BaseAddress = new Uri(apiUrl);
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", opts.GrantType));
nvc.Add(new KeyValuePair<string, string>("scope", scopeasStr));
var content = new FormUrlEncodedContent(nvc);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{apiUrl}/oauth2/token");
requestMessage.Content = content;
var authenticationString = $"{clientId}:{secret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString));
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
var response = await externalHttpClient.SendAsync(requestMessage);
if (response.IsSuccessStatusCode)
{
var value = await response.ParseResponse();
var json = JObject.Parse(value);
json.TryGetValue("access_token", out var v);
var accessToken = v.Value<string>();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}
else
{
var value = await response.ParseResponse();
Console.Write($"{response.StatusCode} - {value}");
Assert.Fail();
}
}
}
2条答案
按热度按时间flseospp1#
基本上,url编码所做的就是将字符串中的一些字符编码为其他预定义的常量。这是基本的替换。我相信你可以创建自己的urlencoding方法,并将其编码为%20,而不是编码为加号。因为%20确实是空格的正确urlencoded等价物。
gk7wooem2#
x1c 0d1x发现有一个替换,它将FormUrlEncodedContent中的+替换为%20。最终生成了我自己的ByteArrayContent/FormUrlEncodedContent版本。