How to add a JavaScript function in WebView and call it later from HTML upon submitting reCAPTCHA

前端 未结 2 1314
萌比男神i
萌比男神i 2021-02-05 22:58

I am adding a JavaScript function in WebView like this (Kotlin):

val webView = findViewById(R.id.webview) as WebView
webView.getSettings().setJavaScriptEnabled(t         


        
相关标签:
2条回答
  • 2021-02-05 23:06

    In order to run your reCaptchaCallbackInAndroid exposed method from JavaScript, when the user submitted a successful reCAPTCHA response, first make sure, to actually listen to the reCAPTCHA callback via g-recaptcha tag attributes:

    <div class="g-recaptcha"
         data-sitekey="{{your site key}}"
         data-callback="myCustomJavaScriptCallback"
    ></div>
    

    or via the reCAPTCHA JavaScript API:

    grecaptcha.render(
      'g-recaptcha-element-id', {
        sitekey: '{{your site key}}',
        callback: 'myCustomJavaScriptCallback'
      }
    )
    

    then, when the page finished loading in the WebView, add your JavaScript callback function to the window object using webView.loadUrl:

    webView.loadUrl("""
        javascript:(function() {
            window.myCustomJavaScriptCallback = function(token) {                
                android.reCaptchaCallbackInAndroid(token);
                /* also add your additional JavaScript functions
                   and additional code in this function */
            }
        })()
    """.trimIndent())
    

    and finally, when the user submits a successful reCAPTCHA response, your myCustomJavaScriptCallback will be called and through that, your exposed reCaptchaCallbackInAndroid method too with the reCAPTCHA token.

    • Since you're using Kotlin, in this case, you can just simply use multiline string literals.

    • Since you're exposing a method to JavaScript, make sure to know the security concerns.

    • In case you'll need additional JavaScript injection in the future (more method exposure, DOM manipulation, etc.), check out this post.


    In your case:

    Set reCAPTCHA to call your captchaResponse JavaScript function via tag attribute:

    <div class="g-recaptcha"
         ...
         data-callback="captchaResponse"
         ...
    ></div>
    

    or via its API:

    grecaptcha.render(
      '...', {
        ...
        callback: 'captchaResponse'
        ...
      }
    )
    

    and add your captchaResponse callback function to window:

    webView.loadUrl("""
        javascript:(function() {
            window.captchaResponse = function(token) {
                android.reCaptchaCallbackInAndroid(token);
                /* also you can add further JavaScript functions
                   and additional code in this function */
            }
        })()
    """.trimIndent())
    

    Test:

    Here's a simple, Empty Activity in Android Studio with a basic LinearLayout ( an EditText and a Button within the layout) and the MainActivity.kt:

    package com.richardszkcs.injectjsintowebview
    
    import android.net.Uri
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.webkit.JavascriptInterface
    import kotlinx.android.synthetic.main.activity_main.*
    import android.webkit.WebView
    import android.webkit.WebViewClient
    import android.widget.Toast
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            sendButton.setOnClickListener { loadWebpage() }
        }
    
        @Throws(UnsupportedOperationException::class)
        fun buildUri(authority: String) : Uri {
            val builder = Uri.Builder()
            builder.scheme("https")
                    .authority(authority)
            return builder.build()
        }
    
        @JavascriptInterface
        fun reCaptchaCallbackInAndroid(token: String) {
            val tok = token.substring(0, token.length / 2) + "..."
            Toast.makeText(this.applicationContext, tok, Toast.LENGTH_LONG).show()
        }
    
        fun loadWebpage() {
            webView.getSettings().setJavaScriptEnabled(true)
            webView.addJavascriptInterface(this, "android")
            webView.getSettings().setBuiltInZoomControls(false)
            webView.loadUrl("https://richardszkcs.github.io/recaptcha-test/")
    
            webView.webViewClient = object : WebViewClient() {
                override fun onPageFinished(view: WebView, url: String) {
                    super.onPageFinished(view, url)
                    webView.loadUrl("""
                        javascript:(function() {
                            window.onCaptchaSuccess = function(token) {
                                android.reCaptchaCallbackInAndroid(token);
                            }
                        })()
                    """.trimIndent())
                }
            }
        }
    }

    then using a simple reCAPTCHA test website, the window.onCaptchaSuccess function is called upon a successful reCAPTCHA submission and the reCAPTCHA token is partially displayed in a Toast using an Android Emulator:


    Full disclosure: I made the reCAPTCHA test website to prepare/test/debug similar situations.

    0 讨论(0)
  • 2021-02-05 23:09

    Try injecting the script like this,

    function addCode(code){
    var addedScript= document.createElement('script');
    addedScript.text= code;
    document.body.appendChild(addedScript);}
    

    now call the function like,

    val codeToExec = "function captchaResponse (token){" +
                        "android.reCaptchaCallbackInAndroid(token);" +
                        "}";
    

    now exec loadurl like,

    webview.loadUrl("javascript:(function addCode(code){
    var addedScript= document.createElement('script');
    addedScript.text= code;
    document.body.appendChild(addedScript);})(codeToExec));
    
    0 讨论(0)
提交回复
热议问题