安卓应用对接MyAnimeList OAuth2授权使用Mal4J获token返回400错误求助
MyAnimeList安卓端OAuth2授权获取访问令牌返回400错误
我正在为我的安卓应用使用OAuth2协议完成MyAnimeList平台的用户身份认证,流程参考官方授权指南,目前遇到获取访问令牌时报400错误的问题,具体实现如下:
步骤1:获取授权码
该步骤我使用WebView引导用户输入账号密码完成授权,目前观测可正常获取返回的authorizationCode,实现代码如下:
private static final String REDIRECT_URL = "http://localhost/oauth"; private static final String CLIENT_ID = "9c..."; // 已省略 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); /* * 完成用户认证前,客户端需要生成Code Verifier和Code Challenge。Code Verifier是高熵加密随机字符串,仅允许包含[A-Z]/[a-z]/[0-9]/"-"/"."/"_"/"~"字符,长度需在43到128字符之间。 * MAL平台仅支持Code Challenge的plain转换规则,即Code Challenge需和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, "重定向到: " + request.getUrl()); Uri url = request.getUrl(); if(url.toString().contains(REDIRECT_URL)){ String authorizationCode = url.getQueryParameter("code"); Log.d(TAG, "获取到授权码: " + 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, "登录地址: " + loginUrl); webview.loadUrl(loginUrl); }
步骤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, "获取令牌出错!", throwable); }) .onErrorComplete() .subscribe(token -> { Log.d(TAG, "--> 访问令牌: " + token.getToken()); Log.d(TAG, "--> 刷新令牌: " + 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$0(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)
补充信息
目前已确认步骤1可正常获取授权码,尝试将code challenge/verifier硬编码为128位连续'A'(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)后问题仍未解决。
抓取到的请求和响应如下:

排查方向
- 回调地址不匹配:这是OAuth2流程中400错误最常见的原因,需要确认三个地方的回调地址完全一致:MAL API申请时填写的回调地址、授权请求中携带的redirect_uri、请求令牌时传入的redirect_uri,包括协议头(http/https)、域名、路径、末尾是否有斜杠都不能有任何差异。
- 授权码过期:MAL的授权码有效期只有几分钟,拿到授权码后必须立刻发起令牌请求,中间不要插入任何耗时操作。
- PKCE参数不匹配:要确认请求授权时的code_challenge和请求令牌时的code_verifier完全相同,中间没有被做任何编码、转义、截断处理,同时字符集必须落在MAL要求的[A-Z]/[a-z]/[0-9]/"-"/"."/"_"/"~"范围内。
- 请求格式错误:令牌接口要求请求头的Content-Type必须为application/x-www-form-urlencoded,可确认下Mal4j发起请求时有没有正确携带这个请求头,参数有没有被错误序列化为JSON格式。
- 客户端参数错误:确认填写的Client ID和MAL后台分配的完全一致,没有多余的空格、换行或者遗漏字符,应用状态在MAL后台为正常启用状态。
内容的提问来源于stack exchange,提问作者Florian Baierl
相关产品推荐
相关产品推荐

