ReCaptcha 在 iPhone 上无法正常工作

Posted

技术标签:

【中文标题】ReCaptcha 在 iPhone 上无法正常工作【英文标题】:ReCaptcha not working properly on iPhone 【发布时间】:2015-05-27 13:18:54 【问题描述】:

我有一个带有简单联系表格的网站。验证有点少,因为它不进入数据库;只是一封电子邮件。表单的工作方式如下:

有 5 个字段 - 其中 4 个是必需的。提交被禁用,直到4个字段都有效,然后您可以提交。然后再次在服务器上验证所有内容,包括recaptcha(我的客户端未验证)。整个过程用ajax完成,有多个测试必须在服务端通过或者返回4**个headers,调用fail回调handler。

在桌面上的 Chrome 上,一切都像 gangbusters 一样工作(我没有尝试过其他浏览器,但我无法想象为什么它们会有所不同),但在 iPhone 上,即使我不检查,reCaptcha 也会始终验证测试用的盒子。

换句话说:我仍然必须正确填写四个值才能提交,但如果我不选中 reCaptcha 框,请求仍然成功。

如果有人认为这会有所帮助,我可以发布一些代码,但似乎问题出在设备而不是代码上。有人对此有任何见解吗?


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


更新:2015 年 5 月 28 日

我仍在调试它,但似乎 Mobile Safari 忽略了我在 iPhone 上的响应标头。当我将响应输出到页面时,我在桌面上得到的(data,status,xhr) 是:

    data我的回复此时只是说错误或成功 -> error

    status:error

    xhr:'error',400,'error'

在移动 Safari 上:

    data:error

    status:success

    xhr:'error',200,'success'

所以 - 它似乎只是忽略了我的响应标题。我尝试明确设置"headers":"cache-control":"no-cache",但无济于事。


更新:2015 年 6 月 3 日

每个请求,这里是代码。这几乎肯定超出了您的需要。由于我为尝试修复它所做的更改,它也变得更加迟钝。另请注意,虽然看起来有些变量尚未定义,但它们(应该)已在其他文件中定义。

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;
    
 

【问题讨论】:

需要用于提交 AJAX 请求的 javascript 来帮助您调试。您是在发布(请说“是”)而不是在获取吗?如果您正在获取并且无法发布,请确保将缓存清除添加到请求中(不要仅依赖标头,因为标头可以被忽略的方式有很多)。但是发布 JS,我们可以从那里提供帮助。 @user1167442 是的,请提供一些代码:) 在这里会有所帮助。 是 - POST 方法。已添加代码。谢谢。 在黑暗中拍摄 - 也许您的 iPhone 日期设置不正确。 能否请您提供一些服务器端代码(您提到的 PHP)?我 100% 确定存在服务器端问题。如果您尝试仅从客户端修复它,恶意黑客仍然可以忽略验证码。 【参考方案1】:

就像那个问题ios: Authentication using XMLHttpRequest - Handling 401 reponse 一样,解决这个问题的最简单方法是忽略自然标头验证,并在回调成功时使用一些标志进行验证。

我见过一些这样的情况,但从来没有闻到好味道。

【讨论】:

【参考方案2】:

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

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

尝试做:

$remoteIp = $_SERVER['REMOTE_ADDR'];

Ajax 只是让浏览器做请求,因此 PHP 可以完美的抓取我们用户的 ip。

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

从不信任任何 Javascript 变量而不是 Ajax 也更安全,因为这些变量也应该被视为用户输入。

【讨论】:

根据我的代码您看不到它,但是在服务器上设置了remoteIP var。但是,无论哪种情况;为什么会根据客户端浏览器而有所不同? 如果它是在客户端计算的,很可能另一个浏览器可能会以不同的方式计算它,将错误的 IP 发送回谷歌。 我没想到javascript甚至可以访问remoteIP。无论哪种方式,它都没有实际意义。在这种情况下,它是在页面加载之前在服务器上计算出来的,并传递给一个 javascript 变量。【参考方案3】:

验证码旨在防止恶意客户端(机器人),因此理论上如果客户端绕过验证码,则属于服务器端问题。(但是,如果客户端无法完成验证码,可能是服务器端问题或客户端问题。)

所以问题一定出在服务器上。即使出于安全考虑,您也应该使用$_SERVER['REMOTE_ADDR'] 而不是$_POST['remoteIp'],因为$_POST['remoteIp'] 可能是伪造的(被恶意客户端)。事实上,$_SERVER['REMOTE_ADDR'] 比客户端的$_POST['remoteIp'] 可靠得多。

【讨论】:

好像你在这里说了两件事。你是说IOS上的Safari不支持验证码吗?另外,我不太明白第二部分如何适用于我的情况。 @user1167442 不。问题是说 Safari 绕过了验证码。理论上如果绕过验证码,一定是服务器端的问题。第二部分提出了一个可能的解决方案。感谢您的通知,我改写了我的答案。【参考方案4】:

我在 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>

通常情况下,它应该可以工作(只需添加您的私钥和公钥),我在 2 秒前在我的 iPhone SE 上进行了测试,并且效果很好。

【讨论】:

以上是关于ReCaptcha 在 iPhone 上无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

Firebase (Flutter) 验证电话号码始终需要 reCAPTCHA

jPlayer 在 iOS iPhone 上无法正常工作

高度在 IOS (iphone) 上无法正常工作

右约束在 iphone SE 上无法正常工作

UIButton Round Corner 在 iPhone 5 上无法正常工作

setNavigationBarHidden 动画在 iPhone X 上无法正常工作