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

How to implement google reCaptcha without inline script?

期望的行为

在我的 webpack 捆绑的 js 文件中合并 reCaptcha JavaScript 代码,而不是通过内联脚本标签。

实际行为

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

Uncaught ReferenceError: grecaptcha is not defined

我试过的

以下 inline 实现有效,我一直在使用 these docs for reference

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

Google 有一个 FAQ about CSP and reCaptcha, but it only applies to automatic rendering, where there is no callback function or 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 开发工具中收到错误:

Uncaught ReferenceError: grecaptcha is not defined

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

如何在不使用内联脚本范例的情况下实现 google reCaptcha?

编辑

我认为 Google 使用 nonce-based approach (also suggested in ) only works if you are doing automatic rendering 的建议(只需要 <script src="****"> 标签)。

如果您像我一样使用 explicit rendering,这需要内联回调函数的定义,那么我认为 nonce 方法行不通。

最后只发布对我有用的内容。

使用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"]
            }
        },
    })
);