CSRF防御实施方案

850 阅读3分钟

Web前端

  1. 每次发送AJAX web请求(包括POST,GET)前,都要产生一个随机token。
  2. 将token以'HALO_TOKEN'标签名,放入每次AJAX请求的Header中,作为http报文一部分发送至服务端。
  3. 对服务端响应的特殊错误码(0008)做相应CSRF风险提示。
token创建
  1. 读取SEED值。SEED可以用sessionId,也可以用固定的值。SEED建议24位,不够用0后补位。

  2. 每次发送服务端请求前,基于SEED和当前客户端系统时间毫秒数,产生一段随机可逆的明文。

    • 随机选取SEED文本长度内的5个数字。
    • 将每个数字对应SEED中的字符内容连同数字组成一个键值对。键值对用':'分隔。
    • 将所有键值对用','串联起来。形成长串文本。
    • 获取当前客户端系统时间毫秒数,用'_'将其添加到文本末尾,最终形成明文。
  3. 用3DES算法对步骤2中产生的明文,以SEED为密钥加密,形成密文。

  4. 用Base64对密文做一次编码操作,形成token。

SEED获取与设置

前端应用开发人员可以通过两种方式获取一个有效的SEED:

sessionID作为SEED
  1. 用户登录成功后,前端程序通过访问"/web/sessionid",从响应报文的data字段中获取当前用户sessionId。
  2. 注意:将sessionId放入浏览器的localStorage中。
localStorage.setItem("_HALOSESSIONID", sessionId);
固定SEED设置
  1. 用户首次访问网站后,由JS将与服务端开发人员约定好的固定SEED值放入localStorage中。

注意:固定SEED值需要与服务器端配合。两方的配置内容要统一。

服务端

  1. 拦截所有SpringMVC的Request请求。
  2. 将token值从header的'HALO_TOKEN'中获取到,如果获取不到则直接抛出(0008)错误。
  3. 对token内容进行解密验证。
  4. 根据解密验证的情况,执行对应的业务逻辑或直接返回(0008)错误。
token解密与验证
  1. 获取当前请求的SEED。
  2. 对token做Base64做解码操作,还原密文。
  3. 采用3DES算法,以SEED为密钥对密文进行解密。
  4. 对解密后的token内容进行验证
    • 对解密后内容的以'_'为分割,对前半段的Hash内容进行验证。要求必须能够与SEED的字符分布情况一致。
    • 对'_'分割的后半段内容,与服务器当前时间毫秒数做差值,要求毫秒数差值不能大于N*60000。N代表分钟数。
SEED读取策略

后端框架会分别尝试两种方式获取SEED值。第一种不成功,就会尝试第二种获取方式,如果两种都无法获取有效SEED,则报错。

  1. 先尝试访问配置项web.csrf.token.seed,如果读取到非空值,则将其作为SEED。
  2. 如果第一种方式失败,则直接获取当前请求对应的sessionId,将其作为SEED。