最近因要将网站快速迁移到安卓端中,于是便开始使用起了webview,虽然大体功能正常,但类似Oauth和文件上传一类的功能却莫名失效
最终经过两天时间也算是妥善解决了,于是写下此文,顺便做一个记录
外部环境:Windows10 Android Studio 夜神模拟器
开发环境:Android12 Api31 Kotlin Gradle

思路分析

正常Oauth2:
网站发送请求到验证端->验证端向回调地址发送code->网站通过code访问服务端并获取用户信息
转跳路径:
网站->第三方登录页面->网站
而在安卓的Webview下,这套逻辑也依然正常工作,只是变成了以下这种形式:
网站(APP)->第三方登录页面->网站(浏览器)
是的,从APP发送过去的Oauth2请求,最后因为回调地址是http/https而转跳到了浏览器,导致APP无法收到回调内容。
好的,以上是思路和错误原因分析,那么接下来,我们再来商讨解决方法。

解决思路

这里作者所用的为QQ的OAuth2,具体操作请按照自身情况调整
在上述分析后,我们的目标便很明确了,就是要修改最终的回调地址,让他把code传递到APP中而非浏览器中。
而同时,作者此处搭建APP端只是为了扩展生态,而并非迁移生态,因此修改得在不影响原功能的情况下进行。
即目标的大概流程如下:
APP->QQ登录界面->网站(浏览器)->APP(schema)
注:此处通过网页转跳回APP是因为QQ的OAuth中回调地址不支持Schema协议,若读者的回调地址支持Schema回调,则直接通过QQ转跳回APP后再进行请求重写即可。

请求修改

为了使回调时能够将参数传入APP,我们需要修改OAuth2的回调地址,而该回调地址是在发送验证请求时进行的,因此我们需要在发送时对该请求进行拦截与重写
如:原本www.example.com/auth地址将会向https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=a&redirect_uri=/auth/callback”
此时我们在APP中对该链接进行拦截

  override fun shouldOverrideUrlLoading(
                view: WebView?,
                request: WebResourceRequest?
            ): Boolean {
                val url: String = request?.url.toString()
                    if (url == "https://example.com/auth" || url == "https://example.com/auth") {
                        val modifiedUrl: String? = modifyUrl1(url)
                        modifiedUrl?.let { view!!.loadUrl(it) }
                        modifiedUrl?.let { Log.v("url result", it) }
                        return true // 表示URL已经被重写了
                    }
}
    fun modifyUrl1(url: String?): String? {
        return url?.replace("auth", "auth2")
    }

这样,传入的auth就被改写为auth2,实现了app路由与网站路由的分离。
接着再修改网站后端,新增对auth2的路由支持,并将新路由中发送的Oauth的回调地址修改为example.com/callback2
同时将该回调地址添加到你的OAuth提供商的回调地址白名单中
之后在callback2网页中编写代码,获取传入的code,之后将获取的code拼接进链接字符串。如:Myapp://homepage/auth?code=xxxxxxxxxx
同时通过Window.location实现schema的转跳,这样code便进入了APP中

截取转跳

首先,我们需要在APP中截取由外部schema转跳进APP的内容,同时将请求中携带的内容发往网站的验证地址

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
     val data = intent.data
        val schemaUri = data.toString()
        if (schemaUri.contains("type=auth")) {       //当链接包含auth时 尝试提取code并loadurl
            val pattern = Pattern.compile("(code=.*?)(&|$)")
            val matcher = pattern.matcher(schemaUri)
            if (matcher.find()) {
                val code: String? = matcher.group(1)
                webView?.loadUrl("$WEB_URL/callback?$code")
                return
            }
        }
        webView?.loadUrl(WEB_URL)
        return
}

此时,由外部调用APP时发送进来的code就会被重定向到网站的callback地址,这样就又回到了原本的验证流程,同时在APP中完成了登录校验。

总结

创造出一条新的OAuth路径,并在初次发送Oauth请求时直接改写原请求使其重定向到APP专用的路线上,随后借用浏览器作为转跳中介,将OAuth回调的Code拦截并发往APP。
以上,本随笔到此结束,下一篇应该是解决Webview在API26~API31的高版本下文件上传的问题吧