ReCaptcha неправильно работает на iPhone

У меня есть сайт с простой формой контакта. Валидация несколько минимальна, поскольку она не входит в базу данных; просто по электронной почте. Форма работает как таковая:

Есть 5 полей – 4 из которых требуются. Представление отключено до тех пор, пока не будут введены 4 поля, и вы сможете отправить их. Затем все снова проверяется на сервере, в том числе recaptcha (который не проверяется мной на стороне клиента). Весь процесс выполняется с помощью ajax, и есть несколько тестов, которые должны проходить на стороне сервера или возвращаются 4 ** заголовка, и вызывается обработчик обратного вызова fail.

Все работает как gangbusters на Chrome на рабочем столе (я не пробовал другие браузеры, но я не могу представить, почему они будут разными), но на iPhone reCaptcha всегда проверяет, даже если я не проверю флажок для тест.

Другими словами: я все равно должен правильно заполнить эти четыре значения для отправки, но если я не поставлю флажок для reCaptcha, запрос все равно будет выполнен.

Я могу опубликовать код, если кто-то подумает, что это будет полезно, но, похоже, проблема связана с устройством, а не с кодом. Кто-нибудь может понять это?


Примечание . Серверная сторона – это PHP / Apache, если это полезно.


Обновление: 28.05.2015 :

Я все еще отлаживаю это, но похоже, что Mobile Safari игнорирует мои заголовки ответов на моем iPhone. Когда я выдаю ответ на страницу, то, что я получаю на рабочем столе для (data,status,xhr) :

  1. data : мой ответ, который в этот момент просто говорит об ошибке или успехе -> error

  2. status : error

  3. xhr : {'error',400,'error'}

На мобильном сафари:

  1. data : error

  2. status : success

  3. xhr : {'error',200,'success'}

Итак – похоже, что это просто игнорирует мои заголовки ответов. Я попытался явно установить {"headers":{"cache-control":"no-cache"}} но безрезультатно.


Обновление: 6/3/2015

За запрос, вот код. Это почти наверняка больше, чем вам нужно. Он также стал более тупым из-за изменений, которые я сделал, чтобы попытаться исправить это. Также обратите внимание, что, хотя может показаться, что существуют переменные, которые не были определены, они (должны) были определены в других файлах.

The client side

  $('#submit').on('click', function(e) { $(this).parents('form').find('input').each(function() { $(this).trigger('blur'); }) var $btn = $(this); $btn = $btn.button('loading'); var dfr = $.Deferred(); if ($(this).attr('disabled') || $(this).hasClass('disabled')) { e.preventDefault(); e.stopImmediatePropagation(); dfr.reject(); return false; } else { var input = $('form').serializeArray(); var obj = {}, j; $.each(input, function(i, a) { if (a.name === 'person-name') { obj.name = a.value; } else if (a.name === 'company-name') { obj.company_name = a.value; } else { j = a.name.replace(/(g-)(.*)(-response)/g, '$2'); obj[j] = a.value; } }); obj.action = 'recaptcha-js'; obj.remoteIp = rc.remoteiP; rc.data = obj; var request = $.ajax({ url: rc.ajaxurl, type: 'post', data: obj, headers: { 'cache-control': 'no-cache' } }); var success = function(data) { $btn.data('loadingText', 'Success'); $btn.button('reset'); $('#submit').addClass('btn-success').removeClass('btn-default'); $btn.button('loading'); dfr.resolve(data); }; var fail = function(data) { var reason = JSON.parse(data.responseText).reason; $btn.delay(1000).button('reset'); switch (reason) { case 'Recaptcha Failed': case 'Recaptcha Not Checked': case 'One Or more validator fields not valid or not filled out': case 'One Or more validator fields is invalid': // reset recaptcha if ($('#submit').data('tries')) { $('#submit').remove(); $('.g-recaptcha').parent().addBack().remove(); myPopover('Your request is invalid. Please reload the page to try again.'); } else { $('#submit').data('tries', 1); grecaptcha.reset(); myPopover('One or more of your entries are invalid. Please make corrections and try again.'); } break; default: // reset page $('#submit').remove(); $('.g-recaptcha').remove(); myPopover('There was a problem with your request. Please reload the page and try again.'); break; } dfr.reject(data); }; request.done(success); request.fail(fail); } 

The Server:

 function _send_email(){ $recaptcha=false; /* * */ if(isset($_POST['recaptcha'])): $gRecaptchaResponse=$_POST['recaptcha']; $remoteIp=isset($_POST['remoteIp']) ? $_POST['remoteIp'] : false; /* ** */ if(!$remoteIp): $response=array('status_code'=>'409','reason'=>'remoteIP not set'); echo json_encode($response); http_response_code(409); exit(); endif; /* ** */ /* ** */ if($gRecaptchaResponse==''): $response=array('status_code'=>'400','reason'=>'Recaptcha Failed'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ if($recaptcha=recaptcha_test($gRecaptchaResponse,$remoteIp)): $recaptcha=true; /* ** */ else: $response=array('status_code'=>'400','reason'=>'Recaptcha Failed'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ /* * */ else: $response=array('status_code'=>'400','reason'=>'Recaptcha Not Checked'); echo json_encode($response); http_response_code(400); exit(); endif; /* * */ /* * */ if($recaptcha==1): $name=isset($_POST['name']) ? $_POST['name'] : false; $company_name=isset($_POST['company_name']) ? $_POST['company_name'] : false; $phone=isset($_POST['phone']) ? $_POST['phone'] : false; $email=isset($_POST['email']) ? $_POST['email'] : false; /* ** */ if(isset($_POST['questions'])): $questions=$_POST['questions']=='' ? 1 : $_POST['questions']; /* *** */ if(!$questions=filter_var($questions,FILTER_SANITIZE_SPECIAL_CHARS)): $response=array('status_code'=>'400','reason'=>'$questions could not be sanitized'); echo json_encode($response); http_response_code(400); exit(); endif; /* *** */ /* ** */ else: $questions=true; endif; /* ** */ /* ** */ if( count( array_filter( array( $name,$company_name,$phone,$email ),"filter_false" ) ) !=4 ): $response=array('status_code'=>'400','reason'=>'One Or more validator fields not valid or not filled out'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ $company_name=filter_var($company_name,FILTER_SANITIZE_SPECIAL_CHARS); $name=filter_var($name,FILTER_SANITIZE_SPECIAL_CHARS); $phone=preg_replace('/[^0-9+-]/', '', $phone); $email=filter_var($email,FILTER_VALIDATE_EMAIL); /* ** */ if($company_name && $recaptcha && $name && $phone && $email && $questions): $phone_str='Phone: ' . $phone; $company_str='Company: ' . $company_name; $email_str='Email String: ' . $email; $name_str='Name: '.$name; $questions=$questions==1 ? '' : $questions; $body="$name_str\r\n\r\n$company_str\r\n\r\n$email_str\r\n\r\n$phone_str\r\n\r\n____________________\r\n\r\n$questions"; $mymail='fake@fake.com'; $headers = array(); $headers[] = "MIME-Version: 1.0"; $headers[] = "Content-type: text/plain; charset=\"utf-8\""; $headers[] = "From: $email"; $headers[] = "X-Mailer: PHP/" . phpversion(); /* *** */ if(mail('$mymail', 'Information Request from: ' . $name,$body,implode("\r\n",$headers))): $response=array('status_code'=>'200','reason'=>'Sent !'); echo json_encode($response); http_response_code(200); exit(); /* *** */ else: $response=array('status_code'=>'400','reason'=>'One Or more validator fields is invalid'); echo json_encode($response); http_response_code(400); exit(); endif; /* *** */ endif; /* ** */ endif; /* * */ $response=array('status_code'=>'412','reason'=>'There was an unknown error'); echo json_encode($response); http_response_code(412); exit(); } function recaptcha_test($gRecaptchaResponse,$remoteIp){ $secret=$itsasecret; //removed for security; require TEMPLATE_DIR . '/includes/lib/recaptcha/src/autoload.php'; $recaptcha = new \ReCaptcha\ReCaptcha($secret); $resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp); if ($resp->isSuccess()) { return true; // verified! } else { $errors = $resp->getErrorCodes(); return false; } } по function _send_email(){ $recaptcha=false; /* * */ if(isset($_POST['recaptcha'])): $gRecaptchaResponse=$_POST['recaptcha']; $remoteIp=isset($_POST['remoteIp']) ? $_POST['remoteIp'] : false; /* ** */ if(!$remoteIp): $response=array('status_code'=>'409','reason'=>'remoteIP not set'); echo json_encode($response); http_response_code(409); exit(); endif; /* ** */ /* ** */ if($gRecaptchaResponse==''): $response=array('status_code'=>'400','reason'=>'Recaptcha Failed'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ if($recaptcha=recaptcha_test($gRecaptchaResponse,$remoteIp)): $recaptcha=true; /* ** */ else: $response=array('status_code'=>'400','reason'=>'Recaptcha Failed'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ /* * */ else: $response=array('status_code'=>'400','reason'=>'Recaptcha Not Checked'); echo json_encode($response); http_response_code(400); exit(); endif; /* * */ /* * */ if($recaptcha==1): $name=isset($_POST['name']) ? $_POST['name'] : false; $company_name=isset($_POST['company_name']) ? $_POST['company_name'] : false; $phone=isset($_POST['phone']) ? $_POST['phone'] : false; $email=isset($_POST['email']) ? $_POST['email'] : false; /* ** */ if(isset($_POST['questions'])): $questions=$_POST['questions']=='' ? 1 : $_POST['questions']; /* *** */ if(!$questions=filter_var($questions,FILTER_SANITIZE_SPECIAL_CHARS)): $response=array('status_code'=>'400','reason'=>'$questions could not be sanitized'); echo json_encode($response); http_response_code(400); exit(); endif; /* *** */ /* ** */ else: $questions=true; endif; /* ** */ /* ** */ if( count( array_filter( array( $name,$company_name,$phone,$email ),"filter_false" ) ) !=4 ): $response=array('status_code'=>'400','reason'=>'One Or more validator fields not valid or not filled out'); echo json_encode($response); http_response_code(400); exit(); endif; /* ** */ $company_name=filter_var($company_name,FILTER_SANITIZE_SPECIAL_CHARS); $name=filter_var($name,FILTER_SANITIZE_SPECIAL_CHARS); $phone=preg_replace('/[^0-9+-]/', '', $phone); $email=filter_var($email,FILTER_VALIDATE_EMAIL); /* ** */ if($company_name && $recaptcha && $name && $phone && $email && $questions): $phone_str='Phone: ' . $phone; $company_str='Company: ' . $company_name; $email_str='Email String: ' . $email; $name_str='Name: '.$name; $questions=$questions==1 ? '' : $questions; $body="$name_str\r\n\r\n$company_str\r\n\r\n$email_str\r\n\r\n$phone_str\r\n\r\n____________________\r\n\r\n$questions"; $mymail='fake@fake.com'; $headers = array(); $headers[] = "MIME-Version: 1.0"; $headers[] = "Content-type: text/plain; charset=\"utf-8\""; $headers[] = "From: $email"; $headers[] = "X-Mailer: PHP/" . phpversion(); /* *** */ if(mail('$mymail', 'Information Request from: ' . $name,$body,implode("\r\n",$headers))): $response=array('status_code'=>'200','reason'=>'Sent !'); echo json_encode($response); http_response_code(200); exit(); /* *** */ else: $response=array('status_code'=>'400','reason'=>'One Or more validator fields is invalid'); echo json_encode($response); http_response_code(400); exit(); endif; /* *** */ endif; /* ** */ endif; /* * */ $response=array('status_code'=>'412','reason'=>'There was an unknown error'); echo json_encode($response); http_response_code(412); exit(); } function recaptcha_test($gRecaptchaResponse,$remoteIp){ $secret=$itsasecret; //removed for security; require TEMPLATE_DIR . '/includes/lib/recaptcha/src/autoload.php'; $recaptcha = new \ReCaptcha\ReCaptcha($secret); $resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp); if ($resp->isSuccess()) { return true; // verified! } else { $errors = $resp->getErrorCodes(); return false; } } 

Подобно этому вопросу iOS: аутентификация с использованием XMLHttpRequest – обработка ответа 401 – самый простой способ решить это – игнорировать проверку правильности заголовков и, при успешном завершении обратного вызова, проверять с помощью некоторого флага.

Я видел такие случаи и никогда не пахнет хорошо.

Правильно ли установлена ​​ваша переменная remoteIP на стороне клиента?

Даже если ваш запрос Ajax отправляет пустое или ложное значение, функция isset () в вашем php-скрипте вернет true и, таким образом, неправильно заполнит $ remoteIp.

Попробуйте сделать:

 $remoteIp = $_SERVER['REMOTE_ADDR']; 

Ajax просто заставляет браузер делать запрос, поэтому PHP может отлично захватить ip нашего пользователя.

Я уверен, что если вы передадите неправильное значение, ReCaptcha будет так или иначе испортиться.

Также безопаснее никогда не доверять никаким переменным Javascript над Ajax, поскольку они также должны рассматриваться как пользовательский ввод.

Капча предназначен для предотвращения вредоносных клиентов (роботов) , поэтому теоретически, если клиент обходит капчу, это проблема на стороне сервера. (Однако, если клиент не может завершить капчу, это может быть проблема на стороне сервера или проблема с клиентской стороной.)

Поэтому проблема должна быть на сервере. Даже из соображений безопасности вы должны использовать $_SERVER['REMOTE_ADDR'] а не $_POST['remoteIp'] потому что $_POST['remoteIp'] может быть фальсифицирован (вредоносным клиентом). Фактически, $_SERVER['REMOTE_ADDR'] намного надежнее, чем клиентская сторона $_POST['remoteIp'] .

Я сделал сценарий 2 или 3 месяца назад, который все еще работает отлично, попробуйте следующее:

 <?php $siteKey = ''; // Public Key $secret = ''; // Private Key /** * This is a PHP library that handles calling reCAPTCHA. * - Documentation and latest version * https://developers.google.com/recaptcha/docs/php * - Get a reCAPTCHA API Key * https://www.google.com/recaptcha/admin/create * - Discussion group * http://groups.google.com/group/recaptcha * * @copyright Copyright (c) 2014, Google Inc. * @link http://www.google.com/recaptcha * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * A ReCaptchaResponse is returned from checkAnswer(). */ class ReCaptchaResponse { public $success; public $errorCodes; } class ReCaptcha { private static $_signupUrl = "https://www.google.com/recaptcha/admin"; private static $_siteVerifyUrl = "https://www.google.com/recaptcha/api/siteverify?"; private $_secret; private static $_version = "php_1.0"; /** * Constructor. * * @param string $secret shared secret between site and ReCAPTCHA server. */ function ReCaptcha($secret) { if ($secret == null || $secret == "") { die("To use reCAPTCHA you must get an API key from <a href='" . self::$_signupUrl . "'>" . self::$_signupUrl . "</a>"); } $this->_secret=$secret; } /** * Encodes the given data into a query string format. * * @param array $data array of string elements to be encoded. * * @return string - encoded request. */ private function _encodeQS($data) { $req = ""; foreach ($data as $key => $value) { $req .= $key . '=' . urlencode(stripslashes($value)) . '&'; } // Cut the last '&' $req=substr($req, 0, strlen($req)-1); return $req; } /** * Submits an HTTP GET to a reCAPTCHA server. * * @param string $path url path to recaptcha server. * @param array $data array of parameters to be sent. * * @return array response */ private function _submitHTTPGet($path, $data) { $req = $this->_encodeQS($data); $response = file_get_contents($path . $req); return $response; } /** * Calls the reCAPTCHA siteverify API to verify whether the user passes * CAPTCHA test. * * @param string $remoteIp IP address of end user. * @param string $response response string from recaptcha verification. * * @return ReCaptchaResponse */ public function verifyResponse($remoteIp, $response) { // Discard empty solution submissions if ($response == null || strlen($response) == 0) { $recaptchaResponse = new ReCaptchaResponse(); $recaptchaResponse->success = false; $recaptchaResponse->errorCodes = 'missing-input'; return $recaptchaResponse; } $getResponse = $this->_submitHttpGet( self::$_siteVerifyUrl, array ( 'secret' => $this->_secret, 'remoteip' => $remoteIp, 'v' => self::$_version, 'response' => $response ) ); $answers = json_decode($getResponse, true); $recaptchaResponse = new ReCaptchaResponse(); if (trim($answers ['success']) == true) { $recaptchaResponse->success = true; } else { $recaptchaResponse->success = false; $recaptchaResponse->errorCodes = $answers [error-codes]; } return $recaptchaResponse; } } $reCaptcha = new ReCaptcha($secret); if(isset($_POST["g-recaptcha-response"])) { $resp = $reCaptcha->verifyResponse( $_SERVER["REMOTE_ADDR"], $_POST["g-recaptcha-response"] ); if ($resp != null && $resp->success) {echo "OK";} else {echo "CAPTCHA incorrect";} } ?> <html> <head> <title>Google reCAPTCHA</title> <script src="https://www.google.com/recaptcha/api.js"></script> </head> <body> <form action="reCAPTCHA.php" method="POST"> <input type="submit" value="Submit"> <div class="g-recaptcha" data-sitekey="<?php echo $siteKey; ?>"></div> </form> </body> </html> 

Обычно он должен работать (просто добавьте свой секретный ключ и ваш открытый ключ), я тестировал на своем iPhone SE 2 секунды назад, и он отлично работал.