如何使用 express.js 在 Ajax 调用中实现 CSRF 保护(寻找完整示例)?

Posted

技术标签:

【中文标题】如何使用 express.js 在 Ajax 调用中实现 CSRF 保护(寻找完整示例)?【英文标题】:How to implement CSRF protection in Ajax calls using express.js (looking for complete example)? 【发布时间】:2012-06-27 08:12:35 【问题描述】:

我正在尝试在使用 express.js 框架的 node.js 构建的应用中实现 CSRF 保护。该应用程序大量使用了对服务器的 Ajax 发布调用。我了解连接框架提供 CSRF 中间件,但我不确定如何在客户端 Ajax 发布请求的范围内实现它。

在 *** 中发布的其他问题中对此有一些零碎的内容,但我还没有找到一个相当完整的示例来说明如何从客户端和服务器端实现它。

有没有人愿意分享一个可行的例子来说明如何实现这一点?我见过的大多数示例都假设您在服务器端呈现表单,然后将其(连同嵌入式 csrf_token 表单字段)发送到客户端。在我的应用程序中,所有内容都通过 Backbone.js 在客户端(包括模板)呈现。服务器所做的只是提供 JSON 格式的值,这些值被客户端 Backbone.js 中的各种模型使用。据我了解,我需要先通过 ajax 检索 csrf_token,然后才能使用它。但是,我担心从安全角度来看这可能是个问题。这是一个有效的担忧吗?

【问题讨论】:

【参考方案1】:

可以通过为 CSRF 令牌添加 meta 标签,然后在每个 Ajax 请求中传递 CSRF 令牌来完成

服务器

添加 CSRF 中间件

app.use(express.csrf());
app.use(function (req, res, next) 
  res.locals.token = req.session._csrf;
  next();
);

您可以通过元标记将 CSRF 令牌传递给客户端。例如,Jade

meta(name="csrf-token", content="#token")

客户

jQuery 有一个称为 ajaxPrefilter 的功能,它允许您提供一个回调,以便在每个 Ajax 请求中调用。然后使用 ajaxPrefilter 设置标题。

var CSRF_HEADER = 'X-CSRF-Token';

var setCSRFToken = function (securityToken) 
  jQuery.ajaxPrefilter(function (options, _, xhr) 
    if (!xhr.crossDomain) 
      xhr.setRequestHeader(CSRF_HEADER, securityToken);
    
  );
;

setCSRFToken($('meta[name="csrf-token"]').attr('content'));

【讨论】:

这里可能是最好的答案 我不同意。这是一个很好的答案,但它并没有解决服务器端渲染的缺乏问题。它提到使用 Jade 在页面上呈现元标记。但是该标签的值需要在服务器端生成,并且由于原始询问者说他的所有视图都不是在服务器端生成的,因此这不起作用。 我们如何在服务器端验证这个令牌? @Bill 我同意。提问者要求客户端渲染的解决方案,服务端只提供restful api。【参考方案2】:

server.js

...
// All Cookies/Sessions/BodyParser go first
app.use(express.csrf());
...
// Get the request
app.post('/ajax', function(req, res)
    res.render('somelayout', csrf_token: req.session._csrf);
);

在 somelayout.jade 中

input(type='hidden', name='_csrf', value=csrf_token)

CSRF 中间件每个会话只生成一次 csrf 令牌,因此在用户访问期间它可能不会改变。

此外,它不会检查 GET 和 HEAD 请求中的令牌。只要令牌在请求中(标头、正文或查询),就可以了。这就是它的全部内容。

【讨论】:

感谢您的快速回复。在我的应用程序中,所有客户端内容都是通过 ajax 执行的。内容(包括模板)的实际呈现在客户端执行。服务器所做的只是以 JSON 格式向客户端提供可变数据。这意味着我必须通过 ajax 检索 CSRF 令牌以将其呈现到页面中,以便可以在 ajax 发布请求中将其发送回。我担心从安全角度来看这可能会有问题,这是一个有效的担忧吗? 如果可以的话,将 CSRF 发送给客户端的一个好方法是将其呈现在 meta 标记(类似于 Rails)或 Express 中的类似标记中。【参考方案3】:

由于您在应用程序中使用Backbone.js,我假设它是一个 SPA,并且您最初加载了一个index.html 文件,然后通过ajax 调用发出任何其他请求。如果是这样,您可以将一小段 JS 代码的 sn-p 添加到您的 index.html 文件中,以保存 crsf 令牌以供将来任何 ajax 调用使用。

例如:

index.html(使用 Handlebars 进行模板...)

<!DOCTYPE html>
<html>
    <head>
        ...
        <script type="text/javascript">
            $( function() 
                window.Backbone.csrf = "csrfToken";
            );
        </script>
    </head>
    <body>
        ...
    </body>
</html>

当你渲染index.html文件时,给它一个express框架在这里生成的csrf令牌:req.session._csrf

当您使用Backbone.js 时,它会设置一个名为Backbone 的全局变量。前一个函数所做的只是将一个名为csrf 的属性设置为全局Backbone 对象。当您对POST 数据进行ajax 调用时,只需将Backbone.csrf 变量作为_csrf 添加到通过ajax 调用发送的数据中。

【讨论】:

【参考方案4】:

在服务器中:

app.use(function (req, res) 
  res.locals._csrf = req.csrfToken();
  res.locals.csrf_form_html = '<input type="hidden" name="_csrf" value="' + req.csrfToken() + '" >';
  req.next();
);

在客户端:(swig 模板)

var csrf =  _csrf|json|safe ;

$.ajaxSetup(
  headers: 
    'X-CSRF-Token': csrf
  
);

$.post("/create", data, function(result) 
  console.log(result);
).fail(function()
  console.log(arguments);
);

【讨论】:

如何在服务器端验证令牌?【参考方案5】:

1.添加csrf保护中间件:

app.use(csrf(cookie: true));

// csrf middleware
app.use(function (req, res, next) 
   res.cookie('X-CSRF-Token', req.csrfToken());
   // this line below is for using csrfToken value in normal forms (as a hidden input)
   res.locals.csrfToken = req.csrfToken(); 
   next();
);

// routing setup goes here

2. 使用 $.ajaxSetup 添加一个 beforeSend 回调:(在所有 ajax 调用之前添加它)

$.ajaxSetup(
beforeSend: function (xhr, settings) 
    function getCookie(name) 
        var cookieValue = null;
        if (document.cookie && document.cookie != '') 
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) 
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) 
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                
            
        
        return cookieValue;
    

    if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) 
        // Only send the token to relative URLs i.e. locally.
        xhr.setRequestHeader("X-CSRF-Token", getCookie('X-CSRF-Token'));
    

);

3. 就是这样!现在您可以发送 ajax 请求,并且不需要在 headers 中添加任何内容或作为请求参数来传递 csrf。

【讨论】:

以上是关于如何使用 express.js 在 Ajax 调用中实现 CSRF 保护(寻找完整示例)?的主要内容,如果未能解决你的问题,请参考以下文章

在 Express JS 中为 Ajax 设置 REST 路由,仅用于 Backbone

Express.js csrf 令牌与 jQuery Ajax

在 Express JS 和 NodeJS 中为 jsonp 回调函数添加参数

phonegap ajax 用户身份验证与 nodejs-express-mongodb-passport js

带有 Express.js 和 jQuery.ajax 的 CORS

express.js 代理