egg.js 通过 egg-security 插件提供了大量的 web 安全防护
使用
框架的安全插件是默认开启的,如果我们想关闭其中一些安全防范,直接设置该项的 enable
属性为 false 即可。例如关闭 xframe 防范:
config/config.default.js
exports.security = {
xframe: {
enable: false,
},
};
security 支持通过 match 和 ignore 配置生效范围,规则和中间件 match、ignore 一致
exports.security = {
csrf: {
ignore: '/example',
},
}
XSS
反射型 xss
反射型的 XSS 攻击,主要是由于服务端接收到客户端的不安全输入,在客户端触发执行从而发起 Web 攻击,防范方式主要是过滤用户的输入
内容输出过滤
当网站需要直接输出用户输入的结果时,使用 helper.escape()
过滤特殊字符
const str = '><script>alert("abc") </script><';
console.log(ctx.helper.escape(str));
// => ><script>alert("abc") </script><
js 内容过滤
根据用户输入内容生成 js 脚本内容时候使用 helper.sjs()
过滤
const foo = '"hello"';
// 未使用 sjs
console.log(`var foo = "${foo}";`);
// => var foo = ""hello"";
// 使用 sjs
console.log(`var foo = "${this.helper.sjs(foo)}";`);
// => var foo = "\\x22hello\\x22";
helper.sjs() 用于在 JavaScript 中输出变量,会对变量中字符进行 JavaScript encode, 将所有非白名单字符转义为
\x
形式,防止 XSS 攻击,也确保在 js 中输出的正确性
json 内容过滤
在 JavaScript 中输出 json 使用helper.sjson()
做 json encode,遍历 json 中的 key,将 value 的值中所有非白名单字符转义为 \x
形式,防止 XSS 攻击
<script>
window.locals = {{ helper.sjson(locals) }};
</script>
存储型 xss
基于存储的 XSS 攻击,是通过提交带有恶意脚本的内容存储在服务器上,当其他人看到这些内容时发起 Web 攻击。egg.js 提供了 helper.shtml()
方法对字符串进行 XSS 过滤
// js
const value = `<a href="http://www.domain.com">google</a><script>evilcode…</script>`;
<html>
<body>
{{ helper.shtml(value) }}
</body>
</html>
// => <a href="http://www.domain.com">google</a><script>evilcode…</script>
CSRF
csrf 主要防范手段是校验服务器生成 csrf token,egg.js 会在 ctx 和 cookie 上生成 cookie 供开发者使用
同步表单
action 上添加 _csrf 参数
<form
method="POST"
action="/upload?_csrf={{ ctx.csrf | safe }}"
enctype="multipart/form-data">
</form>
校验参数可以通过配置修改
// config/config.default.js
module.exports = {
security: {
csrf: {
queryName: '_csrf', // 通过 query 传递 CSRF token 的默认字段为 _csrf
bodyName: '_csrf', // 通过 body 传递 CSRF token 的默认字段为 _csrf
},
},
};
ajax
egg.js 默认 会把 csrf token 会被设置在 Cookie 中,ajax 请求可以从 cookie 中取到 token,放置到 query、body 或者 header 中发送给服务端
var csrftoken = Cookies.get('csrfToken');
$.ajaxSetup({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader('x-csrf-token', csrftoken);
},
});
通过 header 传递 CSRF token 的字段也可以在配置中改变
// config/config.default.js
module.exports = {
security: {
csrf: {
headerName: 'x-csrf-token', // 通过 header 传递 CSRF token 的默认字段为 x-csrf-token
},
},
};
CORS
cors 是解决跨域的最常用手段,egg.js 通过 egg-cors 插件提供了支持
安装
npm i egg-cors --save
启用插件
// config/plugin.js
exports.cors = {
enable: true,
package: 'egg-cors',
};
配置规则
egg-cors 基于 @koa/cors 开发,支持其所有配置
// config/config.default.js
exports.cors = {
// {string|Function} origin: '*',
// {string|Array} allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
// {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`
};
熟悉 cors 的同学知道 cors header Access-Control-Allow-Origin
不支持通配符,配置成 * 会让网站安全形同虚设
egg.js 可以通过两种方式设置动态的 Access-Control-Allow-Origin
函数返回值
// config/config.default.js
exports.cors = {
origin: function ({ req }) {
const { origin } = req.headers;
const whiteList = [
'http://localhost:7001',
'http://127.0.0.1:7001',
];
if (whiteList.includes(origin)) {
return origin;
}
},
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
};
白名单
通过 egg-security 插件配合,支持配置域名白名单动态设置,也就是内置实现了上面函数返回值的设置方式
// config/config.default.js
exports.security = {
domainWhiteList: [
'http://localhost:7001',
'http://127.0.0.1:7001',
],
};
cors 生效还需要客户端
withCredentials
设置和服务器配合,可以参考 阮一峰 cors