Ich füge eine JavaScript-Funktion in WebView hinzu (Kotlin):
val webView = findViewById(R.id.webview) as WebView
webView.getSettings().setJavaScriptEnabled(true)
webView.addJavascriptInterface(this, "Android")
webView.getSettings().setBuiltInZoomControls(false)
webView.loadUrl(url)
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
webView.loadUrl("javascript:(function captchaResponse (token){" +
" Android.reCaptchaCallbackInAndroid(token);" +
" })()")
}
}
Die Funktion funktioniert gut, aber das Problem ist, dass sie sofort ausgeführt wird, wenn ich sie in WebView hinzufüge. Ich möchte es nur als JavaScript-Funktion einschließen und sollte nur vom HTML-Code aus aufgerufen werden, wenn der Benutzer reCAPTCHA ausfüllt. Wie kann ich das machen?
Versuchen Sie, das Skript so zu injizieren,
function addCode(code){
var addedScript= document.createElement('script');
addedScript.text= code;
document.body.appendChild(addedScript);}
rufen Sie jetzt die Funktion auf wie
val codeToExec = "function captchaResponse (token){" +
"Android.reCaptchaCallbackInAndroid(token);" +
"}";
jetzt exec loadurl wie,
webview.loadUrl("javascript:(function addCode(code){
var addedScript= document.createElement('script');
addedScript.text= code;
document.body.appendChild(addedScript);})(codeToExec));
Wenn der Benutzer eine erfolgreiche reCAPTCHA-Antwort gesendet hat, um Ihre reCaptchaCallbackInAndroid
-Methode in JavaScript auszuführen, stellen Sie zunächst sicher, dass Sie tatsächlich die reCAPTCHA-callback
-Antwort über g-recaptcha
Tag-Attribute :
<div class="g-recaptcha"
data-sitekey="{{your site key}}"
data-callback="myCustomJavaScriptCallback"
></div>
oder über die reCAPTCHA JavaScript API :
grecaptcha.render(
'g-recaptcha-element-id', {
sitekey: '{{your site key}}',
callback: 'myCustomJavaScriptCallback'
}
)
fügen Sie nach dem Laden der Seite in WebView
Ihre JavaScript-Rückruffunktion zum window
-Objekt hinzu, indem Sie webView.loadUrl
verwenden:
webView.loadUrl("""
javascript:(function() {
window.myCustomJavaScriptCallback = function(token) {
Android.reCaptchaCallbackInAndroid(token);
/* also add your additional JavaScript functions
and additional code in this function */
}
})()
""".trimIndent())
und schließlich, wenn der Benutzer eine erfolgreiche reCAPTCHA-Antwort einreicht, wird Ihre myCustomJavaScriptCallback
-Methode aufgerufen, und damit auch Ihre exponierte reCaptchaCallbackInAndroid
-Methode mit der reCAPTCHA-Methode token
.
Da Sie Kotlin verwenden, können Sie in diesem Fall einfach mehrzeilige String-Literale verwenden.
Da Sie eine Methode für JavaScript verfügbar machen, müssen Sie das Sicherheitsbedenken kennen.
Falls Sie in Zukunft zusätzliche JavaScript-Injection benötigen (mehr Methodenexposition, DOM-Manipulation usw.), siehe diesen Beitrag .
Stellen Sie reCAPTCHA so ein, dass Ihre JavaScript-Funktion captchaResponse
über tag attribute
aufgerufen wird:
<div class="g-recaptcha"
...
data-callback="captchaResponse"
...
></div>
oder über seine API
:
grecaptcha.render(
'...', {
...
callback: 'captchaResponse'
...
}
)
und fügen Sie Ihre captchaResponse
-Rückruffunktion zu window
hinzu:
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())
Hier ist ein einfaches Empty Activity
in Android Studio mit einem grundlegenden LinearLayout
(ein EditText
und ein Button
innerhalb des Layouts)und dem 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())
}
}
}
}
wenn dann eine einfache reCAPTCHA-Testwebsite verwendet wird, wird die window.onCaptchaSuccess
-Funktion bei erfolgreicher reCAPTCHA-Übermittlung aufgerufen und das reCAPTCHA-Token wird teilweise in einem Toast
mit einem Android Emulator:
Vollständige Offenlegung: Ich habe die reCAPTCHA-Test-Website erstellt, um ähnliche Situationen vorzubereiten/zu testen/zu debuggen.
IN Java
MyApplication.Java
public class MyApplication extends Application {
public static final String TAG = MyApplication.class
.getSimpleName();
private RequestQueue mRequestQueue;
private static MyApplication mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public static synchronized MyApplication getInstance() {
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req, String tag) {
// set the default tag if tag is empty
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
getRequestQueue().add(req);
}
public <T> void addToRequestQueue(Request<T> req) {
req.setTag(TAG);
getRequestQueue().add(req);
}
public void cancelPendingRequests(Object tag) {
if (mRequestQueue != null) {
mRequestQueue.cancelAll(tag);
}
}
}
**MainActivity.Java**
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
// TODO - replace the SITE KEY with yours
private static final String SAFETY_NET_API_SITE_KEY = " YOUR SITE KEY HEAR";
// TODO - replace the SERVER URL with yours
private static final String URL_VERIFY_ON_SERVER = "https://example.com/index.php";
@BindView(R.id.input_feedback)
EditText inputFeedback;
@BindView(R.id.layout_feedback_form)
LinearLayout layoutFeedbackForm;
@BindView(R.id.message_feedback_done)
TextView messageFeedbackDone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(getString(R.string.feedback));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Toast.makeText(getApplicationContext(), "Always check Android Studio `LogCat` for errors!", Toast.LENGTH_LONG).show();
}
@OnClick(R.id.btn_send)
public void validateCaptcha() {
String feedback = inputFeedback.getText().toString().trim();
// checking for empty feedback message
if (TextUtils.isEmpty(feedback)) {
Toast.makeText(getApplicationContext(), "Enter feedback!", Toast.LENGTH_LONG).show();
return;
}
// Showing reCAPTCHA dialog
SafetyNet.getClient(this).verifyWithRecaptcha(SAFETY_NET_API_SITE_KEY)
.addOnSuccessListener(this, new OnSuccessListener<SafetyNetApi.RecaptchaTokenResponse>() {
@Override
public void onSuccess(SafetyNetApi.RecaptchaTokenResponse response) {
Log.d(TAG, "onSuccess");
if (!response.getTokenResult().isEmpty()) {
// Received captcha token
// This token still needs to be validated on the server
// using the SECRET key
verifyTokenOnServer(response.getTokenResult());
}
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
Log.d(TAG, "Error message: " +
CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()));
} else {
Log.d(TAG, "Unknown type of error: " + e.getMessage());
}
}
});
}
/**
* Verifying the captcha token on the server
* Post param: recaptcha-response
* Server makes call to https://www.google.com/recaptcha/api/siteverify
* with SECRET Key and Captcha token
*/
public void verifyTokenOnServer(final String token) {
Log.d(TAG, "Captcha Token" + token);
StringRequest strReq = new StringRequest(Request.Method.POST,
URL_VERIFY_ON_SERVER, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d(TAG, response.toString());
try {
JSONObject jsonObject = new JSONObject(response);
boolean success = jsonObject.getBoolean("success");
String message = jsonObject.getString("message");
if (success) {
// Congrats! captcha verified successfully on server
// TODO - submit the feedback to your server
layoutFeedbackForm.setVisibility(View.GONE);
messageFeedbackDone.setVisibility(View.VISIBLE);
} else {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Json Error: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error: " + error.getMessage());
}
}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("recaptcha-response", token);
return params;
}
};
MyApplication.getInstance().addToRequestQueue(strReq);
}
}
build.gradle
// SafetyNet reCAPTCHA
implementation 'com.google.Android.gms:play-services-safetynet:11.8.0'
// ButterKnife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<Android.support.design.widget.CoordinatorLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
tools:context=".MainActivity">
<Android.support.design.widget.AppBarLayout
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:theme="@style/AppTheme.AppBarOverlay">
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</Android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</Android.support.design.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical"
Android:padding="@dimen/activity_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="info.androidhive.recaptcha.MainActivity"
tools:showIn="@layout/activity_main">
<LinearLayout
Android:id="@+id/layout_feedback_form"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:orientation="vertical">
<TextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:text="@string/title_form"
Android:textColor="#666666"
Android:textSize="20dp"
Android:textStyle="bold" />
<TextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:text="@string/desc_form" />
<EditText
Android:id="@+id/input_feedback"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginTop="@dimen/activity_margin"
Android:gravity="top"
Android:hint="@string/hint_feedback"
Android:lines="5" />
<Button
Android:id="@+id/btn_send"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_marginTop="@dimen/activity_margin"
style="@style/Widget.AppCompat.Button.Colored"
Android:text="@string/btn_send"
Android:textColor="@Android:color/white" />
</LinearLayout>
<TextView
Android:id="@+id/message_feedback_done"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginTop="40dp"
Android:gravity="center"
Android:padding="@dimen/activity_margin"
Android:text="@string/message_feedback_done"
Android:textSize="22dp"
Android:visibility="gone" />
</LinearLayout>