对 'My Anime List' 的 OAuth2 授权无效

OAuth2 authorization to 'My Anime List' not working

我正在尝试使用 Oauth2(遵循此 guide)为我的 Android 应用程序验证“我的动漫列表”用户。

第 1 步: 获取授权令牌

在这里,我使用 WebView 提示用户输入用户名和密码。据我所知,这一步似乎有效。

private static final String REDIRECT_URL = "http://localhost/oauth";
private static final String CLIENT_ID = "9c..."; // omitted
private static final String OAUTH_BASE_URL = "https://myanimelist.net/v1/oauth2/";

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    /*
     * Before you can authenticate a user, your client needs to generate a Code Verifier and a
     * Code Challenge. A Code Verifier is a high-entropy, cryptographic, random string
     * containing only the characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~".
     * The length of the string must be between 43 and 128 characters.
     *
     * MAL only allows the plain transformation for the Code Challenge.
     * In other words, it means that you have to set the Code Challenge equal to the
     * Code Verifier.
     */
    String codeChallenge = PKCEGenerator.generateVerifier(128);
    
    webview = findViewById(R.id.login_webview);
    webview.getSettings().setJavaScriptEnabled(true);
    webview.setWebViewClient(new WebViewClient(){
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){
            Log.d(TAG, "Redirecting to: " + request.getUrl());
            Uri url = request.getUrl();
            if(url.toString().contains(REDIRECT_URL)){
                String authorizationCode = url.getQueryParameter("code");
                Log.d(TAG, "Received authorization code: " + authorizationCode);
                webview.setVisibility(View.GONE);
                getUserAccessToken(authorizationCode, codeChallenge);
            }
            return false; 
        }
    });
    authenticateMAL(codeChallenge);
}

private void authenticateMAL(String codeChallenge) {
    Log.d(TAG, "Code challenge (" + codeChallenge.length() + "): " + codeChallenge);
    String loginUrl = OAUTH_BASE_URL + "authorize" +
            "?response_type=code" +
            "&redirect_uri=" + REDIRECT_URL +
            "&client_id=" + CLIENT_ID +
            "&code_challenge=" + codeChallenge;
    Log.d(TAG, "Login url: " + loginUrl);
    webview.loadUrl(loginUrl);
}

据我所知,这很有效。我得到了预期的 authorizationCode

步骤 2: 获取用户访问令牌和刷新令牌

这里,我使用 Mal4J 进行下一个身份验证步骤:

private void getUserAccessToken(String authorizationCode, String codeChallenge) {

    Single.fromCallable(() -> {
        MyAnimeListAuthenticator authenticator = new MyAnimeListAuthenticator(
                CLIENT_ID, null, authorizationCode, codeChallenge);
        return authenticator.getAccessToken();
    })
        .subscribeOn(Schedulers.io())
        .doOnError(throwable -> {
            Log.e(TAG, "Error while retrieving token!", throwable);
        })
        .onErrorComplete()
        .subscribe(token -> {
            Log.d(TAG, "--> access token: " + token.getToken());
            Log.d(TAG, "--> refresh token: " + token.getRefreshToken());
        });

}

不幸的是,这会导致以下错误:

E/LoginActivity: Error while retrieving token!
    com.kttdevelopment.mal4j.HttpException: Server returned code 400 from 'https://myanimelist.net/v1/oauth2/token': 
        at com.kttdevelopment.mal4j.MyAnimeListAuthenticator.parseToken(MyAnimeListAuthenticator.java:505)
        at com.kttdevelopment.mal4j.MyAnimeListAuthenticator.<init>(MyAnimeListAuthenticator.java:139)
        at florian.baierl.daily_anime_news.ui.LoginActivity.lambda$getUserAccessToken[=13=](LoginActivity.java:99)
        at florian.baierl.daily_anime_news.ui.-$$Lambda$LoginActivity$-bBBIb9OKRzdaFNsFkQdJSeVW74.call(Unknown Source:4)
        at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:43)
        at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4813)
        at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
        at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614)
        at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
        at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

关于为什么会发生这种情况有什么想法吗?我是否缺少 Oauth2 的一些 Android 特定内容?据我所知,我正确地从步骤 1 中检索了授权代码。在那之后,我的代码看起来非常简单,所以我看不出错误在哪里。非常感谢任何提示!

编辑:

这是请求的样子(来自 android 工作室资料视图):

这是回复:

编辑 2:

将代码 challenge/verifier 硬编码到 128 次 'A' (AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) 也不会改变行为:

当您在授权请求中包含 redirect_uri 时,您还需要将其包含在 /token 请求中。也许不是那样。