在ProcessWire中让Ajax请求支持CSRF保护的方法
在ProcessWire中启用CSRF这篇文章写过CSRF的防护方法,但是在ajax请求中用ajax动态引入csrfToken到表单中是完全不行的,这时候我们需要通过headers
发送认证的特殊处理
首先在文件/wire/core/SessionCSRF.php中有这么一段关于Ajax请求验证的实现代码:
if($config->ajax && isset($_SERVER[$headerName]) && $_SERVER[$headerName] === $tokenValue) { $valid = true; } else if($input->post($tokenName) === $tokenValue) { $valid = true; }
实现过程
准备前端Token
首先我们需要生成Token,如果是动态页面只需要拿到token
$csrfTokenName = $session->CSRF->getTokenName(); $csrfTokenValue = $session->CSRF->getTokenValue();
在页面中加入这行
<div class="js-csrf-token" style="display:none;" data-csrf-token-name="<?php echo $csrfTokenName; ?>" data-csrf-token-value="<?php echo $csrfTokenValue; ?>"></div>
如果像我一样用了phpFastCache生成出纯静态文件,绕过了ProcessWire系统,那么需要服务端生成出Token
然后通过Ajax引入Token数据并在前端生成出上面的代码。
最后在前端JS文件中拿数据,我们用这个函数来实现
function getCsrfToken() { const csrfTokenNode = document.getElementsByClassName('js-csrf-token')[0]; return { name: csrfTokenNode.getAttribute('data-csrf-token-name'), value: csrfTokenNode.getAttribute('data-csrf-token-value'), } }
这是第一步,也是最重要的一步。
构建带有Headers的Ajax验证请求
构建headers
const csrfToken = getCsrfToken(); let xhr = new XMLHttpRequest(); // ... // open the request and do some configuration // ... xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // tell the server and PW that it's an AJAX request xhr.setRequestHeader('X-' + csrfToken.name, csrfToken.value); // create a header with the token
需要注意的是,如果使用jQuery,添加headers
参数即可。
$.ajax({ url: "/test", headers: {"X-Test-Header": "test-value"} });
后端请求验证
最后到服务端来接收Token验证
if ($session->CSRF->hasValidToken()) { // works for AJAX! }
如果要自定义token名,可以这么处理
$csrfTokenName = $session->CSRF->getTokenName('myAjaxToken'); // this creates a token if one with the given id doesn't exist $csrfTokenValue = $session->CSRF->getTokenValue('myAjaxToken');
验证
f ($session->CSRF->hasValidToken('myAjaxToken') { // ... }