使用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,步骤如下:
- 在OAuth服务端的后台配置这个redirect_uri(必须完全匹配,包括Scheme、host、path)
- 在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>
- 把你的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(); } }); } } });
代码优化点说明:
- 用
shouldOverrideUrlLoading替代onPageFinished拦截回调:onPageFinished是页面加载完成后才触发,而shouldOverrideUrlLoading是在URL跳转前就拦截,更及时也更可靠,不会因为页面加载失败导致错过回调 - 完善错误处理:不仅判断
access_denied,还通过error参数统一处理授权失败的情况 - 区分自定义Scheme和OOB模式的处理逻辑:两种模式分别对应不同的code获取方式
- 修复了原代码里的错误:原代码里同时调用了
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




