如何在没有内嵌脚本的情况下实现Google reCaptcha?

时间:2020-09-05 14:03:53

标签: javascript jquery recaptcha

期望的行为

将reCaptcha JavaScript代码整合到我的webpack捆绑的js文件中,而不是通过内嵌脚本标签。

实际行为

我在Chrome开发工具中遇到此错误:

未捕获的ReferenceError:未定义grecaptcha

我尝试过的事情

以下inline的实现工作正常,我一直在使用these docs for reference

但是,我必须将unsafe-inline添加到我的script-src内容安全策略中,才能运行内联脚本。更具体地说,这是通过onLoadCallback函数实现explicit rendering所必需的。

Google有一个FAQ about CSP and reCaptcha,但仅适用于automatic rendering,那里没有回调函数或defined parameters

我不想不必使用内联脚本。

index.html

<head>
    <script type="text/javascript">
    var onloadCallback = function() {
        grecaptcha.render('g-recaptcha', {
            'sitekey': '******',
            'size': 'compact'
        });
    };
    </script>
</head>
<body>
    <div id="g-recaptcha"></div>
    <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
</body>
</html>

但是,当我尝试像这样将JS添加到我的entry.js文件中时(停止使用内联脚本):

index.html

<head>
    <script type="module" src="/js/bundle.js"></script>
    <script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>
</head>
<body>
    <div id="g-recaptcha"></div>
</body>
</html>

entry.js

const onloadCallback = () => {
    grecaptcha.render('g-recaptcha', {
        'sitekey': '******',
        'size': 'compact',
        'data-callback': 'ok-you-can-submit-the-form',
        'data-expired-callback': 'you-have-to-click-recaptcha-again',
        'data-error-callback': 'something-went-wrong-please-try-again'
    });
}

$(document).ready(function() {

    onloadCallback();

}

我在Chrome开发工具中收到错误:

未捕获的ReferenceError:未定义grecaptcha

所以我想这是因为bundle.js不了解<head>部分中定义的Recaptcha脚本或其相关变量。

如何在不使用嵌入式脚本范例的情况下实现google reCaptcha?

修改

我认为Google建议使用nonce-based approach(也在this SO answer中建议)仅在您进行automatic rendering(仅需要<script src="****">标记)的情况下有效。

如果您原样使用explicit rendering,则需要内联定义回调函数,那么我认为现时方法不起作用。

1 个答案:

答案 0 :(得分:1)

只需发布最终对我有用的内容即可。

使用reCAPTCHA v3

index.html头:

<script src="https://www.google.com/recaptcha/api.js?render=*******"></script>

点击事件:

grecaptcha.ready(function() {
    grecaptcha.execute('*******', { action: 'submit_entry' }).then(function(token) {
        parameters.token = token;
        ajax_api_entries_post(parameters);
    });
});

从服务器验证Google令牌:

var token = req.body.token;
var url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
var response = await fetch(url);
var response_json = await response.json();

var score = response_json.score;

// see: https://developers.google.com/recaptcha/docs/v3#interpreting_the_score
if (score >= 0.5) { ...

helmet配置:

app.use(
    helmet({
        contentSecurityPolicy: {
            directives: {
                defaultSrc: ["'self'"],
                scriptSrc: ["'self'", "https://maps.googleapis.com", "https://www.google.com", "https://www.gstatic.com"],
                connectSrc: ["'self'", "https://some-domain.com", "https://some.other.domain.com"],
                styleSrc: ["'self'", "fonts.googleapis.com", "'unsafe-inline'"],
                fontSrc: ["'self'", "fonts.gstatic.com"],
                imgSrc: ["'self'", "https://maps.gstatic.com", "https://maps.googleapis.com", "data:", "https://another-domain.com"],
                frameSrc: ["'self'", "https://www.google.com"]
            }
        },
    })
);