egg.js 通过 egg-security 插件提供了大量的 web 安全防护

使用

框架的安全插件是默认开启的,如果我们想关闭其中一些安全防范,直接设置该项的 enable 属性为 false 即可。例如关闭 xframe 防范:
config/config.default.js

  1. exports.security = {
  2. xframe: {
  3. enable: false,
  4. },
  5. };

security 支持通过 match 和 ignore 配置生效范围,规则和中间件 match、ignore 一致

  1. exports.security = {
  2. csrf: {
  3. ignore: '/example',
  4. },
  5. }

XSS

反射型 xss

反射型的 XSS 攻击,主要是由于服务端接收到客户端的不安全输入,在客户端触发执行从而发起 Web 攻击,防范方式主要是过滤用户的输入

内容输出过滤

当网站需要直接输出用户输入的结果时,使用 helper.escape() 过滤特殊字符

  1. const str = '><script>alert("abc") </script><';
  2. console.log(ctx.helper.escape(str));
  3. // => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;

js 内容过滤

根据用户输入内容生成 js 脚本内容时候使用 helper.sjs() 过滤

  1. const foo = '"hello"';
  2. // 未使用 sjs
  3. console.log(`var foo = "${foo}";`);
  4. // => var foo = ""hello"";
  5. // 使用 sjs
  6. console.log(`var foo = "${this.helper.sjs(foo)}";`);
  7. // => var foo = "\\x22hello\\x22";

helper.sjs() 用于在 JavaScript 中输出变量,会对变量中字符进行 JavaScript encode, 将所有非白名单字符转义为 \x 形式,防止 XSS 攻击,也确保在 js 中输出的正确性

json 内容过滤

在 JavaScript 中输出 json 使用helper.sjson() 做 json encode,遍历 json 中的 key,将 value 的值中所有非白名单字符转义为 \x 形式,防止 XSS 攻击

  1. <script>
  2. window.locals = {{ helper.sjson(locals) }};
  3. </script>

存储型 xss

基于存储的 XSS 攻击,是通过提交带有恶意脚本的内容存储在服务器上,当其他人看到这些内容时发起 Web 攻击。egg.js 提供了 helper.shtml() 方法对字符串进行 XSS 过滤

  1. // js
  2. const value = `<a href="http://www.domain.com">google</a><script>evilcode…</script>`;
  1. <html>
  2. <body>
  3. {{ helper.shtml(value) }}
  4. </body>
  5. </html>
  6. // => <a href="http://www.domain.com">google</a>&lt;script&gt;evilcode…&lt;/script&gt;

CSRF

csrf 主要防范手段是校验服务器生成 csrf token,egg.js 会在 ctx 和 cookie 上生成 cookie 供开发者使用

同步表单

action 上添加 _csrf 参数

  1. <form
  2. method="POST"
  3. action="/upload?_csrf={{ ctx.csrf | safe }}"
  4. enctype="multipart/form-data">
  5. </form>

校验参数可以通过配置修改

  1. // config/config.default.js
  2. module.exports = {
  3. security: {
  4. csrf: {
  5. queryName: '_csrf', // 通过 query 传递 CSRF token 的默认字段为 _csrf
  6. bodyName: '_csrf', // 通过 body 传递 CSRF token 的默认字段为 _csrf
  7. },
  8. },
  9. };

ajax

egg.js 默认 会把 csrf token 会被设置在 Cookie 中,ajax 请求可以从 cookie 中取到 token,放置到 query、body 或者 header 中发送给服务端

  1. var csrftoken = Cookies.get('csrfToken');
  2. $.ajaxSetup({
  3. beforeSend: function(xhr, settings) {
  4. xhr.setRequestHeader('x-csrf-token', csrftoken);
  5. },
  6. });

通过 header 传递 CSRF token 的字段也可以在配置中改变

  1. // config/config.default.js
  2. module.exports = {
  3. security: {
  4. csrf: {
  5. headerName: 'x-csrf-token', // 通过 header 传递 CSRF token 的默认字段为 x-csrf-token
  6. },
  7. },
  8. };

CORS

cors 是解决跨域的最常用手段,egg.js 通过 egg-cors 插件提供了支持

安装

  1. npm i egg-cors --save

启用插件

  1. // config/plugin.js
  2. exports.cors = {
  3. enable: true,
  4. package: 'egg-cors',
  5. };

配置规则

egg-cors 基于 @koa/cors 开发,支持其所有配置

  1. // config/config.default.js
  2. exports.cors = {
  3. // {string|Function} origin: '*',
  4. // {string|Array} allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
  5. // {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`
  6. };

熟悉 cors 的同学知道 cors header Access-Control-Allow-Origin 不支持通配符,配置成 * 会让网站安全形同虚设
egg.js 可以通过两种方式设置动态的 Access-Control-Allow-Origin

函数返回值

  1. // config/config.default.js
  2. exports.cors = {
  3. origin: function ({ req }) {
  4. const { origin } = req.headers;
  5. const whiteList = [
  6. 'http://localhost:7001',
  7. 'http://127.0.0.1:7001',
  8. ];
  9. if (whiteList.includes(origin)) {
  10. return origin;
  11. }
  12. },
  13. allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
  14. };

白名单

通过 egg-security 插件配合,支持配置域名白名单动态设置,也就是内置实现了上面函数返回值的设置方式

  1. // config/config.default.js
  2. exports.security = {
  3. domainWhiteList: [
  4. 'http://localhost:7001',
  5. 'http://127.0.0.1:7001',
  6. ],
  7. };

cors 生效还需要客户端 withCredentials 设置和服务器配合,可以参考 阮一峰 cors