编辑:为了澄清,获取授权代码按预期工作。这纯粹是将授权代码交换为失败的令牌的步骤。
我正在尝试使用PKCE流实现授权代码,以便使用spotify API进行身份验证。我知道这里有很多库,但我真的想自己实现它。我所说的流程是:https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-代码流和代码交换pkce的验证密钥我能够创建链接,将用户重定向到同意页面并获得授权代码。然而,当我尝试将此代码换成令牌时,我收到了一个400错误请求,消息为“invalidclient_secret”。这使我相信Spotify假设我正在尝试使用常规的授权代码流,因为客户端机密根本不是PKCE流的一部分。我怀疑我编码的code_verifier或code_challenge错误。我在SO上找到了这个答案(如何计算PCKE的code_verifier?)并将其转换为C#,对Base64编码的哈希产生相同的结果,但它仍然不起作用。
下面是我的生成code_verifier和code_challenge的代码,以及请求交换代码的代码。
代码验证器:
private string GenerateNonce()
{
const string chars = "abcdefghijklmnopqrstuvwxyz123456789";
var random = new Random();
var nonce = new char[100];
for (int i = 0; i < nonce.Length; i++)
{
nonce[i] = chars[random.Next(chars.Length)];
}
return new string(nonce);
}
代码挑战:
private string GenerateCodeChallenge(string codeVerifier)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
return Convert.ToBase64String(hash).Replace("+/", "-_").Replace("=", "");
}
交换令牌:
var parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", ClientId ),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("redirect_uri", "http://localhost:5000"),
new KeyValuePair<string, string>("code_verifier", codeVerifier)
};
var content = new FormUrlEncodedContent(parameters );
var response = await HttpClient.PostAsync($"https://accounts.spotify.com/api/token", content);
这是符合rfc-7636标准的GenerateNonce
(现为GenerateCodeVerifier
)和GenerateCodeChallenge
的重构,它集成到一个可以实例化或用于其静态方法的类中。
/// <summary>
/// Provides a randomly generating PKCE code verifier and it's corresponding code challenge.
/// </summary>
public class Pkce
{
/// <summary>
/// The randomly generating PKCE code verifier.
/// </summary>
public string CodeVerifier;
/// <summary>
/// Corresponding PKCE code challenge.
/// </summary>
public string CodeChallenge;
/// <summary>
/// Initializes a new instance of the Pkce class.
/// </summary>
/// <param name="size">The size of the code verifier (43 - 128 charters).</param>
public Pkce(uint size = 128)
{
CodeVerifier = GenerateCodeVerifier(size);
CodeChallenge = GenerateCodeChallenge(CodeVerifier);
}
/// <summary>
/// Generates a code_verifier based on rfc-7636.
/// </summary>
/// <param name="size">The size of the code verifier (43 - 128 charters).</param>
/// <returns>A code verifier.</returns>
/// <remarks>
/// code_verifier = high-entropy cryptographic random STRING using the
/// unreserved characters[A - Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
/// from Section 2.3 of[RFC3986], with a minimum length of 43 characters
/// and a maximum length of 128 characters.
///
/// ABNF for "code_verifier" is as follows.
///
/// code-verifier = 43*128unreserved
/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
/// ALPHA = %x41-5A / %x61-7A
/// DIGIT = % x30 - 39
///
/// Reference: rfc-7636 https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
///</remarks>
public static string GenerateCodeVerifier(uint size = 128)
{
if (size < 43 || size > 128)
size = 128;
const string unreservedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
Random random = new Random();
char[] highEntropyCryptograph = new char[size];
for (int i = 0; i < highEntropyCryptograph.Length; i++)
{
highEntropyCryptograph[i] = unreservedCharacters[random.Next(unreservedCharacters.Length)];
}
return new string(highEntropyCryptograph);
}
/// <summary>
/// Generates a code_challenge based on rfc-7636.
/// </summary>
/// <param name="codeVerifier">The code verifier.</param>
/// <returns>A code challenge.</returns>
/// <remarks>
/// plain
/// code_challenge = code_verifier
///
/// S256
/// code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
///
/// If the client is capable of using "S256", it MUST use "S256", as
/// "S256" is Mandatory To Implement(MTI) on the server.Clients are
/// permitted to use "plain" only if they cannot support "S256" for some
/// technical reason and know via out-of-band configuration that the
/// server supports "plain".
///
/// The plain transformation is for compatibility with existing
/// deployments and for constrained environments that can't use the S256
/// transformation.
///
/// ABNF for "code_challenge" is as follows.
///
/// code-challenge = 43 * 128unreserved
/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
/// ALPHA = % x41 - 5A / %x61-7A
/// DIGIT = % x30 - 39
///
/// Reference: rfc-7636 https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
/// </remarks>
public static string GenerateCodeChallenge(string codeVerifier)
{
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
return Base64UrlEncoder.Encode(challengeBytes);
}
}
}
对于那些进入单元测试的人来说。
/// <summary>
/// Pkce unit test.
/// </summary>
/// <remarks>
/// MethodName_StateUnderTest_ExpectedBehavior
/// Arrange, Act, Assert
/// </remarks>
[TestFixture]
public class PkceUnitTests
{
[Test]
public void GenerateCodeVerifier_DefaultSize_Returns128CharacterLengthString()
{
string codeVerifier = Pkce.GenerateCodeVerifier();
Assert.That(codeVerifier.Length, Is.EqualTo(128));
}
[Test]
public void GenerateCodeVerifier_Size45_Returns45CharacterLengthString()
{
string codeVerifier = Pkce.GenerateCodeVerifier(45);
Assert.That(codeVerifier.Length, Is.EqualTo(45));
}
[Test]
public void GenerateCodeVerifier_SizeLessThan43_ReturnsDefault128CharacterLengthString()
{
string codeVerifier = Pkce.GenerateCodeVerifier(42);
Assert.That(codeVerifier.Length, Is.EqualTo(128));
}
[Test]
public void GenerateCodeVerifier_SizeGreaterThan128_ReturnsDefault128CharacterLengthString()
{
string codeVerifier = Pkce.GenerateCodeVerifier(42);
Assert.That(codeVerifier.Length, Is.EqualTo(128));
}
[Test]
public void GenerateCodeVerifier_DefaultSize_ReturnsLegalCharacterLengthString()
{
const string unreservedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
for (int x = 0; x < 1000; x++)
{
string codeVerifier = Pkce.GenerateCodeVerifier();
for (int i = 0; i < codeVerifier.Length; i++)
{
Assert.That(unreservedCharacters.IndexOf(codeVerifier[i]), Is.GreaterThan(-1));
}
}
}
[Test]
public void GenerateCodeChallenge_GivenCodeVerifier_ReturnsCorrectCodeChallenge()
{
string codeChallenge = Pkce.GenerateCodeChallenge("0t4Rep04AxvISWM3rMxGnyla2ceDT71oMzIK0iGEDgOt5.isAGW6~2WdGBUxaPYXA6R8vbSBcgSI-jeK_1yZgVfEXoFa1Ec3gPn~Anqwo4BgeXVppo.fjtU7y2cwq_wL");
Assert.That(codeChallenge, Is.EqualTo("czx06cKMDaHQdro9ITfrQ4tR5JGv9Jbj7eRG63BKHlU"));
}
[Test]
public void InstantiateClass_WithDefaultSize_Returns128CharacterLengthCodeVerifier()
{
Pkce pkce = new Pkce();
Assert.That(pkce.CodeVerifier.Length, Is.EqualTo(128));
}
[Test]
public void InstantiateClass_WithSize57_Returns57CharacterLengthCodeVerifier()
{
Pkce pkce = new Pkce(57);
Assert.That(pkce.CodeVerifier.Length, Is.EqualTo(57));
}
}
我复制了代码,并使其能够工作。以下是关于github的工作项目:https://github.com/michaeldisaro/TestSpotifyPkce.
我所做的更改:
public class Code
{
public static string CodeVerifier;
public static string CodeChallenge;
public static void Init()
{
CodeVerifier = GenerateNonce();
CodeChallenge = GenerateCodeChallenge(CodeVerifier);
}
private static string GenerateNonce()
{
const string chars = "abcdefghijklmnopqrstuvwxyz123456789";
var random = new Random();
var nonce = new char[128];
for (int i = 0; i < nonce.Length; i++)
{
nonce[i] = chars[random.Next(chars.Length)];
}
return new string(nonce);
}
private static string GenerateCodeChallenge(string codeVerifier)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
var b64Hash = Convert.ToBase64String(hash);
var code = Regex.Replace(b64Hash, "\\+", "-");
code = Regex.Replace(code, "\\/", "_");
code = Regex.Replace(code, "=+$", "");
return code;
}
}
我在重定向到/授权之前调用Init,重定向url上有:
public async Task OnGet(string code,
string state,
string error)
{
var httpClient = _httpClientFactory.CreateClient();
var parameters = new Dictionary<string, string>
{
{"client_id", "*****************"},
{"grant_type", "authorization_code"},
{"code", code},
{"redirect_uri", "https://localhost:5001/SpotifyResponse"},
{"code_verifier", Code.CodeVerifier}
};
var urlEncodedParameters = new FormUrlEncodedContent(parameters);
var req = new HttpRequestMessage(HttpMethod.Post, "https://accounts.spotify.com/api/token") { Content = urlEncodedParameters };
var response = await httpClient.SendAsync(req);
var content = response.Content;
}
替换正确的正则表达式就可以了。看来问题是“=”,只有最后一个必须更换。
该函数不完整,我只是在内容变量上观看,里面有令牌。接受它,做任何你喜欢的事情。
我想使用PHP实现多个授权,以便与Telegram REST API进行交互。 我要解决的是什么任务?嗯,很简单:几十个用户(他们都有一个carma(+10,-2,+1000等),有相关的组分类:web-masters和customers)在我的网站上有一个用户配置文件。在他们达到一定数量的carma之后,并且由于他们在他们的个人资料中被授权,他们就会加入到基于自动生成的电报的私人聊天中。 经过一
这是我多次尝试向https://accounts.spotify.com/api/token. 范围设置为“播放列表-修改-公共,播放列表-修改-私有”。 我使用的是Python 3.7,Django 2.1.3。 无论我做什么,response_data都会返回{'error': 'invalid_client'} 我尝试了很多方法,包括按照Spotify官方文档在请求正文中传递client_i
根据PKCE规范,OAuth提供者使用code\u验证器来避免中间人攻击。我的理解是,将OAuth代码交换为令牌是基于JavaScript的单页应用程序(SPA)的最佳选择。 当我用谷歌应用编程接口进行实验时,它说“client_secret不见了”。 这是HTTP请求和响应。 ID: 1地址:https://oauth2.googleapis.com/tokenHttp-Method: POST
我们必须在我的客户项目中集成OAuth2.0授权代码授予。目前,该应用程序使用本地登录页面。我们需要删除该页面,并将未登录的用户重定向到AS登录页面,。在AS end成功登录后,我们将被重定向到配置的。此时,我的客户端应用程序将如何知道用户已在AS登录?如何在客户端维护会话?另外,我需要用和访问令牌交换auth代码,并将其用于后续的服务器API调用。那么如何实现这一点并将令牌作为标头发送呢? 应用
我在使用授权代码授予授权对 spotify 网页 API 进行授权时遇到问题。我知道我必须填写我的客户端ID,客户端密码并将uri重定向到字符串,但我不知道如何获取字符串,这是获取访问令牌所必需的称为代码的字符串。 然后 你知道如何获得这个代码并成功获得访问令牌吗? 谢谢
我正在用Java编写一个应用程序,该应用程序与SpotifyWebAPI配合使用,以获取当前播放的专辑的专辑插图(将来可能还有其他内容,因此范围很长)。根据Spotify的指南,我必须使用回调才能获得访问令牌。然而,在使用授权链接时,Spotify给了我以下非常有用且有深刻见解的错误消息。Spotify错误消息 我用来调用open a window的代码是 类似的问题也有人问过,他们的问题是他们的