0%

Egg.js_POST 提交表单时的 CSRF 安全验证

Egg.js 在 POST 提交表单数据时会自动进行 CSRF 安全验证,需要进行配置。
参考:Egg.js 文档:安全威胁 CSRF 的防范

表单页面

方法一:渲染表单时传入 csrf 值

路由配置

配置 /app/router.js 文件:

'use strict';

module.exports = app => {
  const { router, controller } = app;

  // 显示表单页面的路由
  router.get('/', controller.home.index);
  // 提交表单数据的路由
  router.post('/add', controller.home.add);
};

控制器配置

配置 /app/controller/home.js 文件:

'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {

  // 显示表单页面
  async index() {

    // 显示表单页面,加载并渲染 HTML 模版文件
    // this.ctx.csrf 用户访问页面时生成的密钥
    await this.ctx.render('home', { csrf: this.ctx.csrf });
  }

  // 接收 post 提交的数据
  async add() {

    // 获取提交数据
    console.log(this.ctx.request.body);
  }
}

module.exports = HomeController;

模版文件

配置模版文件,因为在提交表单时,需要验证返回动态的 CSRF 值:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <!-- _csrf=<%=csrf%> 表示回传 csrf 值 -->

<form action=/add?_csrf=<%=csrf%> method=post>

用户名:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span>
密码:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>提交<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>

</form>

</body>
</html>

:hexoPostRenderEscape–>

方法二:通过中间件方式进行全局配置

路由配置

/app/router.js 文件同上,不需要更改:

'use strict';

module.exports = app => {
  const { router, controller } = app;

  // 显示表单页面的路由
  router.get('/', controller.home.index);
  // 提交表单数据的路由
  router.post('/add', controller.home.add);
};

中间件配置

配置 /app/middleware/auth.js 中间件,配置 csrf 全局变量:

'use strict';

module.exports = (option, app) => {
  return async function auth(ctx, next) {
    // 设置模版的全局变量
    ctx.state.csrf = ctx.csrf;
    await next();
  };
};

中间件配置好之后,还需要在 config.default.js 文件中引入:

'use strict';

/**
 * @param {Egg.EggAppInfo} appInfo app info
 */
module.exports = appInfo => {
  /**
   * built-in config
   * @type {Egg.EggAppConfig}
   **/
  const config = exports = {};

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1556127582848_8657';

  // 💡💡💡 配置添加中间件 💡💡💡
  config.middleware = [ 'auth' ];

  // 配置模版引擎
  config.view = {
    mapping: {
      '.html': 'ejs',
    },
  };

  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
  };

  return {
    ...config,
    ...userConfig,
  };
};

控制器配置

配置 /app/controller/home.js 文件:

'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {

  // 显示表单页面
  async index() {

    // 显示表单页面,加载并渲染 HTML 模版文件
    // ⚠️ csrf 会在全局进行配置,无需每个表单单独配置
    await this.ctx.render('home');
  }

  // 接收 post 提交的数据
  async add() {

    // 获取提交数据
    console.log(this.ctx.request.body);
  }
}

module.exports = HomeController;

模版文件

配置模版文件,因为在提交表单时,需要验证返回动态的 CSRF 值:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>

    <form action="/add" method="post">

    <!-- 通过隐藏表单域的方式回传 csrf 值 -->
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>_csrf<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&lt;%=csrf%><span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
用户名:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span>
密码:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>提交<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>

</form>

</body>
</html>

:hexoPostRenderEscape–>

欢迎关注我的其它发布渠道