You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用WebView实现OAuth2授权时无法获取authCode的问题求助

解决WebView实现OAuth授权获取authCode的问题

先帮你拆解下问题的核心点,再一步步解决:

一、关于if (url.contains("?code=") && authComplete != true)的疑问

这个判断的作用是识别授权成功后的回调URL,并且确保只处理一次授权结果

  • url.contains("?code="):用来检测当前WebView加载的URL是否包含授权服务器返回的code参数(也就是授权成功的标志)
  • authComplete != true:是个防重复标记,避免因为WebView可能的页面重跳重复解析code,导致多次触发后续逻辑

它没触发的原因大概率是你的回调URL没被正确拦截到,或者授权服务器返回的code参数位置不是?code=(比如可能是&code=,如果URL里还有其他前置参数),后面会详细说怎么优化这个判断逻辑。

二、redirect_uri的正确设置方式

redirect_uri的选择要和你在OAuth服务端配置的完全一致,这是核心前提!

  • 如果用http://localhost:有些OAuth服务端可能不允许本地地址作为回调(尤其是生产环境),而且Android的WebView默认不会处理localhost的跳转,可能直接忽略或者报错
  • 如果用urn:ietf:wg:oauth:2.0:oob:这是"out-of-band"模式,也就是授权后服务器会把code直接显示在页面上,而不是跳转,这种情况你需要解析页面内容来获取code,而不是通过URL参数

推荐方案:使用自定义Scheme
比如设置为yourapp://oauth/callback,步骤如下:

  1. 在OAuth服务端的后台配置这个redirect_uri(必须完全匹配,包括Scheme、host、path)
  2. 在AndroidManifest.xml里给你的Activity添加对应的Intent Filter,用来拦截这个Scheme的跳转:
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="yourapp" android:host="oauth" android:path="/callback" />
</intent-filter>
  1. 把你的authURL里的redirect_uri改成yourapp://oauth/callback

三、修复你的WebView代码,正确获取authCode

你的代码存在几个关键问题,导致无法获取code,下面是优化后的版本:

final Dialog webDialog = new Dialog(this);
Objects.requireNonNull(webDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
webDialog.setContentView(R.layout.dialog_purchase_webview);
WebView webView = webDialog.findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setLoadsImagesAutomatically(true);
webSettings.setJavaScriptEnabled(true);
webSettings.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);

// 替换成你配置好的redirect_uri
String authURL = "https://www.apixyz.com/oauth/authorize?client_id=xyz&redirect_uri=yourapp://oauth/callback&response_type=code";
webView.loadUrl(authURL);

ImageButton webCloseBtn = webDialog.findViewById(R.id.close_btn);
webCloseBtn.setOnClickListener(v -> webDialog.dismiss());

webDialog.show();

webView.setWebViewClient(new WebViewClient() {
    boolean authComplete = false;

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        String url = request.getUrl().toString();
        // 优先拦截自定义Scheme的回调URL,比onPageFinished更可靠
        if (url.startsWith("yourapp://oauth/callback")) {
            Uri uri = Uri.parse(url);
            String authCode = uri.getQueryParameter("code");
            String error = uri.getQueryParameter("error");

            if (authCode != null && !authComplete) {
                authComplete = true;
                Log.i("OAuth", "CODE : " + authCode);
                Intent resultIntent = new Intent();
                resultIntent.putExtra("code", authCode);
                setResult(Activity.RESULT_OK, resultIntent);
                Toast.makeText(getApplicationContext(),"Authorization Code is: " + authCode, Toast.LENGTH_SHORT).show();
                webDialog.dismiss();
                return true; // 告诉WebView不要加载这个URL
            } else if (error != null && error.equals("access_denied")) {
                authComplete = true;
                Log.i("OAuth", "ACCESS_DENIED_HERE");
                setResult(Activity.RESULT_CANCELED, new Intent());
                Toast.makeText(getApplicationContext(), "Error Occured", Toast.LENGTH_SHORT).show();
                webDialog.dismiss();
                return true;
            }
        }
        // 其他URL正常加载
        return super.shouldOverrideUrlLoading(view, request);
    }

    // 保留onPageFinished作为备用,处理oob模式的情况
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        // 如果用的是urn:ietf:wg:oauth:2.0:oob,这里解析页面内容获取code
        if (url.startsWith("urn:ietf:wg:oauth:2.0:oob") && !authComplete) {
            // 这里需要根据页面结构来提取code,比如用view.evaluateJavascript获取页面文本
            view.evaluateJavascript("document.body.textContent", value -> {
                // 假设code显示在页面文本里,需要做字符串处理提取
                String authCode = value.replace("\"", "").trim();
                if (!authCode.isEmpty()) {
                    authComplete = true;
                    Log.i("OAuth", "CODE from OOB: " + authCode);
                    Intent resultIntent = new Intent();
                    resultIntent.putExtra("code", authCode);
                    setResult(Activity.RESULT_OK, resultIntent);
                    Toast.makeText(getApplicationContext(),"Authorization Code is: " + authCode, Toast.LENGTH_SHORT).show();
                    webDialog.dismiss();
                }
            });
        }
    }
});

代码优化点说明:

  1. shouldOverrideUrlLoading替代onPageFinished拦截回调onPageFinished是页面加载完成后才触发,而shouldOverrideUrlLoading是在URL跳转前就拦截,更及时也更可靠,不会因为页面加载失败导致错过回调
  2. 完善错误处理:不仅判断access_denied,还通过error参数统一处理授权失败的情况
  3. 区分自定义Scheme和OOB模式的处理逻辑:两种模式分别对应不同的code获取方式
  4. 修复了原代码里的错误:原代码里同时调用了setResult(Activity.RESULT_OK, resultIntent)setResult(Activity.RESULT_CANCELED, resultIntent),这会导致结果被覆盖,现在已经修正

四、额外注意事项

  • 确保你的OAuth服务端已经正确配置了redirect_uri,大小写、路径都要完全匹配
  • 如果测试时WebView跳转到了浏览器,检查是否在WebViewClient里正确拦截了URL,或者是否有其他应用拦截了你的自定义Scheme
  • 对于HTTPS的授权URL,确保你的Android版本支持对应的SSL证书,调试时可暂时忽略证书验证(生产环境不建议)

内容的提问来源于stack exchange,提问作者Ashish Jain

火山引擎 最新活动