sensai2.0

324
15

1. SPA web应用与传统web开发的区别

传统的web网站往往在渲染时将服务端生成的csrf token注入到页面或提交表单下的<input type='hidden' />的标签里,提交表单或者ajax提交数据后,页面刷新,同时csrf_token更新

SPA应用于传统web开发最大的区别在于无页面刷新,所以csrf_token往往只有在页面首屏时获取,然后在提交表单后进行更新,并将新的csrf_token从接口中返回然后在本地状态更新。


2. csrf的生成、存储与更新

2.1 报文体

spa应用的初始信息一般都会包括当前用户的个人数据。个人数据的信息一般有两种方式获取:一种是在渲染首屏layout的时候顺带吐出,另一种方式是在首屏layout加载完成后通过ajax接口拉取得到(页面完全与数据分离的方式)。无论通过哪种方式获取,一般都是根据浏览器中代表用户token的cookie作为令牌来拉取用户信息的(当然这个cookie一般也是https加http-only的)。

生成csrf_token的前提是用户已登录,所以完全可以把csrf_token作为个人数据的一个字段来处理。

示意报文如下所示:

Request:


Response

当然如果用户未登录,那么一般服务端直接返回{"isLogin": false}或者{}就可以了,此时也不需要返回csrf_token


2.2 浏览器端

csrf_token一般作为spa应用的全局状态表中使用,当某个接口需要csrf_token的时候,便将状态表中该字段放入请求。

一般请求成功后服务端会更新csrf_token并返回,此时浏览器需要获取返回的参数并更新全局状态表。

对于使用状态管理框架的应用(如redux/mobx等),可以在请求action的处理方法中做更新。

毋庸置疑,csrf_token的检测应当与业务逻辑尽量减少耦合,服务端和浏览器都应当采用filter或者中间件的形式进行解耦。浏览器在发起请求前将csrf_token拼装进业务报文,在收到response后先行解包。

更简单的方式是进一步封装ajax请求的接口,给每个请求加一个x-csrf-token的header(无论服务端是否需要),如果服务端验证并更新了该参数,则会在response中同样包含x-csrf-token,最终浏览器更新状态。

示例代码如下:

// returns a promise
const actionFetch = async (apiName, method, body) => {
  return fetch(API_PREFIX + apiName, {
    method,
    headers: {
      'content-type': 'application/json',
      'x-csrf-token': globalState.get('csrf_token') || 'empty'
    },
    body: JSON.stringify(body)
    credentials: 'include', // add cookies
  }).then(res => {
    // 处理状态码/业务错误码等
// .......
if (res.headers['x-csrf-token']) { globalState.update('csrf_token', res.headers['x-csrf-token']) } return res.json() }).catch(error => { return error }) }
在使用Promise.all同时请求两个需要验证csrf_token的接口时一般会出现后者验证失败的问题,因为服务端处理前一个请求后会返回新的csrf_token,而后面的请求用的还是旧的csrf_token

这种case是需要避免的。一般来讲服务端只需要给update、delete、insert这些参数做csrf校验。


2.3 服务器端

SPA应用服务端csrf生成与校验的方式与传统web开发基本相同:csrf_token生成的方法放置在全局中间件,csrf_token校验的方法作为中间件/filter置于需要校验的接口,不同的web框架基本都有类似的处理方案,在框架csrf组件基础上做一些调整即可满足需求

在层级明确的大型web应用中,后端开发人员往往只需要提供restful api而不关心csrf校验相关的操作,所以csrf_token往往是在web server端生成并校验,而因为auth token需要实时调用后端接口获取,所以很多web server没有使用到session的存储后端(memcache/redis等),因此无法在session中存储csrf token

这种情况下csrf_token一般是通过对auth_token返回的相关用户数据进行加盐非对称加密来生成,这种csrf_token一般不会验完一次就更新,当然这不意味着站点是不安全的(除非加密方式被破解)


3. 一些其他的处理方案

最近看到不少spa应用直接采取类似restful api的方式与服务端通信。

由于浏览器fetch接口和xmlhttprequest接口可以传入PUT、DELETE等各种请求method,且很多后端接口与页面url不再同一个域上,所以很多应用已经抛弃了web server,不再用cookie存储用户token,直接将用户token传入到接口参数body中。

采取这种方式通信完全​可以无需担心csrf攻击,因为不再通过cookie传输用户token了。

但是造成了别的问题,即用户token对浏览器js可见了,一旦页面上存在持久性xss漏洞,那么攻击者可以立刻获取到用户token直接入侵用户账户。同时第三方统计工具也可以轻松的获取到用户的登陆token,这造成了不小的隐患。

就个人而言,不太推崇这种传参的方式,除非是内网应用或者web端设置了csp头并且所有的统计工具都是自己家的,否则最好不要采取这种方法。

回复

对话列表

×