验证码在iPhone上不能正常工作


ReCaptcha not working properly on iPhone

我有一个简单的联系方式的网站。验证在某种程度上是最小的,因为它不进入数据库;只是一封电子邮件。这个表单是这样工作的:

有5个字段-其中4个是必需的。提交是禁用的,直到4个字段是有效的,然后你可以提交。然后在服务器上再次验证所有内容,包括验证码(这不是由我的客户端验证)。整个过程是通过ajax完成的,并且有多个测试必须在服务器端通过,否则将返回4**报头,并调用失败回调处理程序。

在桌面版Chrome上一切都运行得很好(我没有尝试过其他浏览器,但我无法想象为什么它们会有所不同),但在iPhone上,即使我不勾选测试框,reCaptcha也总是有效的。

换句话说:为了提交,我仍然必须正确填写这四个值,但是如果我不选中验证码框,请求仍然成功。

我可以张贴一些代码,如果有人认为这将是有帮助的,但似乎问题是与设备,而不是与代码。有人对此有什么见解吗?


注意:如果有帮助,服务器端是PHP/Apache。


更新日期:5/28/2015:

我还在调试这个,但似乎移动Safari忽略了我的iPhone上的响应头。当我输出对页面的响应时,我在桌面上得到的(data,status,xhr)是:

  1. data: 我的响应,此时只是说错误或成功 -> error

  2. status: error

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

Mobile safari:

  1. data: error

  2. status: success

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

所以-它似乎只是忽略了我的响应头。我尝试显式设置{"headers":{"cache-control":"no-cache"}},但无济于事。


更新:6/3/2015

Per Request,下面是代码。这几乎肯定比你需要的要多。它也变得更加迟钝,因为我所做的改变,试图修复它。还要注意,虽然有些变量似乎没有定义,但它们(应该)已经在其他文件中定义了。

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;
    }
 }

就像这个问题iOS:使用XMLHttpRequest进行身份验证-处理401响应最简单的解决方法是忽略自然标头验证,在回调成功时,使用某些标志进行验证。

我见过一些这样的病例,闻起来从来都不好闻。

您的"remoteIP"变量是否在客户端正确设置?

即使Ajax请求发送一个空值或false值,php脚本中的isset()函数也会返回true,从而错误地填充$remoteIp。

尝试:

$remoteIp = $_SERVER['REMOTE_ADDR'];

Ajax只是让浏览器执行请求,因此PHP可以完美地获取用户的ip。

我敢肯定,如果你传递错误的值,ReCaptcha会以这样或那样的方式搞砸。

永远不要信任任何Javascript变量而不是Ajax,因为这些变量也应该被视为用户输入。

captcha旨在防止恶意客户端(机器人),所以理论上如果客户端绕过captcha,这是一个服务器端问题。(然而,如果客户端未能完成验证码,可能是服务器端问题或客户端问题)

所以问题一定出在服务器上。即使从安全方面考虑,也应该使用$_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秒前,它工作得很好。