OAuth2 和 PKCE - 代码验证器无效

问题描述 投票:0回答:3

我正在制作一个 Java 应用程序,尝试为需要 PKCE(代码交换证明密钥)的 API (Etsy.com) 实现 OAuth2 授权流程。我已经尝试了一段时间,但在交换 OAuth 令牌的访问代码时,我一直遇到以下错误:

{"error":"invalid_grant","error_description":"code_verifier is invalid"}

这是生成传递 sha256 编码验证器的初始 URL 的方法:

@Override
public String getOAuth2AuthorizationUrl() {

    String sha256hex = null;
    try {
        if(_currentVerifier == null)
            _currentVerifier = PkceUtil.generateCodeVerifier();

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] encodedhash = digest.digest(
                _currentVerifier.getBytes(StandardCharsets.UTF_8));
        sha256hex = new String(encodedhash);
    } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }

    UnicodeEscaper basicEscaper = new PercentEscaper("-", false);
    System.out.println("sha256="+sha256hex);
    System.out.println("veri="+_currentVerifier);

    String oAuth2AuthorizationUrl = super.getOAuth2AuthorizationUrl();
    var authUrl = oAuth2AuthorizationUrl
            +"&state=asdf&code_challenge_method=S256&code_challenge='"
            + basicEscaper.escape(sha256hex)+"'";
    return authUrl;
}

然后我发送请求以交换令牌代码作为 application/x-www-form-urlencoded POST-request:

    UnicodeEscaper basicEscaper = new PercentEscaper("-", false);
 var params = "grant_type=authorization_code&client_id="
            +token.getOwner().clientId+"&redirect_uri="
            +basicEscaper.escape("https://localhost")
            +"&code="+accessCode+"&code_verifier="+basicEscaper.escape(_currentVerifier)+"";

我尝试了很多变体,也尝试将值包装在 '' 等中,但没有任何效果。我是否误解了这个过程?我最初发送 sha256 字符串,当请求 OAuth2 令牌时,我发送编码的值。 有什么想法吗?

更新: 我尝试使用 Google 库来生成哈希,但仍然遇到相同的错误。

Hasher hasher = Hashing.sha256().newHasher();
    hasher.putString(CODE_VERIFIER, Charsets.UTF_8);
    HashCode sha256 = hasher.hash();

    System.out.println("veri="+AppUtils.encodeBase64(AppUtils.toHexString(sha256.asBytes())));

    String oAuth2AuthorizationUrl = super.getOAuth2AuthorizationUrl();

    var authUrl = oAuth2AuthorizationUrl
            +"&state=asdf&code_challenge_method=S256&code_challenge="+AppUtils.encodeBase64(AppUtils.toHexString(sha256.asBytes()));
    return authUrl;

示例中的 code_challenge 参数的格式看起来也不同。

java oauth pkce
3个回答
1
投票

您的挑战生成不正确。验证者的哈希值必须是 base64 url 编码

标准说

code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

所以这是错误的:

sha256hex = new String(encodedhash);

应该是:

sha256hex = Base64.getUrlEncoder().withoutPadding().encodeToString(encodedhash);

另外,不要在 URL 中的挑战周围加引号。

旁注:没有必要逃避挑战和验证器,因为它们是以 url 安全的方式编码的


0
投票

我知道这个问题已经很老了,但我希望它能拯救像我这样的人。

code_verifier is invalid
实际上与
PKCE verification failed
不同(One keycloak 给我使用了错误的验证器)。

标准规定您的密钥长度应至少为 43 个字符,最多不超过 128 个字符。您还应该加密已经基本编码的版本。这段代码对我有用:

public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
        String inp = encoder.encodeToString(Utils.generateString(43).getBytes());
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        String res = encoder.encodeToString(digest.digest(inp.getBytes()));
        System.out.println("Challenge: " + StringEscapeUtils.escapeHtml3(res) + "\nVerifier: " + inp);
    }
}



public class Utils {
    public static Random random = new Random();
    public static String[] characters;

    static {
        String charS = "abcdefghijklmnopqrstuvwxyz";
        charS+=charS.toUpperCase() + "01234567890";
        characters = charS.split("");
    }


    public static String generateString(int length) {
        StringBuilder result = new StringBuilder();
        for(int now = 0; now<length; now++) {
            result.append(characters[random.nextInt(characters.length)]);
        }
        return result.toString();
    }
}

0
投票

我遇到了很多麻烦,因为即使我阅读并遵循 Etsy 在 grantflow 授权中的文档(我认为是不折不扣的),我所尝试的方法也不起作用。

您应该仔细阅读快速入门指南的这一部分,其中 Etsy 开发人员展示了他们如何执行 PKCE 的代码示例:https://developers.etsy.com/documentation/tutorials/quickstart/#generate-the-pkce-code-挑战

我在最初的程序中所做的(不起作用)是在规范范围内生成随机字符串,并使用标准的 Base64 编码器。您可以在 Etsy 的代码片段(复制如下)中看到,他们修改了常规的 base64 以删除两个不能很好地编码到 URL 中的字符。此外,它们使用 JS 函数生成 32 个随机字节,这可能与连接 32 个字符串字符不同。不管怎样,你应该尝试将这段代码从 JS 翻译成 Java,看看它是如何工作的。

我认为字节编码很重要的另一个原因是,在 Etsy 的 API 中,他们引用了此 Web 规范 https://datatracker.ietf.org/doc/html/rfc7636#appendix-B 并且它使用哈希的八位字节值虽然您在调用 base64 编码函数时可能使用不同的基数。

const crypto = require("crypto");

// The next two functions help us generate the code challenge
// required by Etsy’s OAuth implementation.
const base64URLEncode = (str) =>
  str
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");

const sha256 = (buffer) => crypto.createHash("sha256").update(buffer).digest();

// We’ll use the verifier to generate the challenge.
// The verifier needs to be saved for a future step in the OAuth flow.
const codeVerifier = base64URLEncode(crypto.randomBytes(32));

// With these functions, we can generate
// the values needed for our OAuth authorization grant.
const codeChallenge = base64URLEncode(sha256(codeVerifier));
const state = Math.random().toString(36).substring(7);

console.log(`State: ${state}`);
console.log(`Code challenge: ${codeChallenge}`);
console.log(`Code verifier: ${codeVerifier}`);
console.log(`Full URL: https://www.etsy.com/oauth/connect?response_type=code&redirect_uri=http://localhost:3003/oauth/redirect&scope=email_r&client_id=1aa2bb33c44d55eeeeee6fff&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`)

今天我花了几个小时试图解决这个问题,终于找到了可行的解决方案。在寻找答案的过程中,我发现了这个问题,我想留下我所做的事情,以防其他人遇到这个问题。

© www.soinside.com 2019 - 2024. All rights reserved.