保护服务器渲染的应用程序 (Securing server-rendered applications)
如果您正在使用 AdonisJS 创建服务器渲染的应用程序,那么您必须使用 @adonisjs/shield 软件包来保护您的应用程序免受常见的 Web 攻击,如 CSRF、XSS、内容嗅探等。
该软件包已在 web starter kit 中预先配置。但是,您可以按如下方式手动安装和配置该软件包。
@adonisjs/shield 软件包与 @adonisjs/session 软件包具有对等依赖关系,因此请确保首先配置会话软件包。
node ace add @adonisjs/shield
-
使用检测到的包管理器安装
@adonisjs/shield软件包。 -
在
adonisrc.ts文件中注册以下服务提供者。{providers: [// ...其他提供者() => import('@adonisjs/shield/shield_provider'),]} -
创建
config/shield.ts文件。 -
在
start/kernel.ts文件中注册以下中间件。router.use([() => import('@adonisjs/shield/shield_middleware')])
CSRF 保护
CSRF (跨站请求伪造) 是一种攻击,恶意网站欺骗您的 Web 应用程序用户在未经其明确同意的情况下执行表单提交。
为了防止 CSRF 攻击,您应该定义一个隐藏的输入字段,其中包含只有您的网站才能生成和验证的 CSRF 令牌值。因此,由恶意网站触发的表单提交将失败。
保护表单
配置 @adonisjs/shield 软件包后,所有没有 CSRF 令牌的表单提交都将自动失败。因此,您必须使用 csrfField edge 助手定义一个包含 CSRF 令牌的隐藏输入字段。
Edge 助手
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="Enter your name">
<button type="submit"> Submit </button>
</form>
输出 HTML
<form method="POST" action="/">
<input type="hidden" name="_csrf" value="Q9ghWSf0-3FD9eCiu5YxvKaxLEZ6F_K4DL8o"/>
<input type="name" name="name" placeholder="Enter your name"/>
<button type="submit">Submit</button>
</form>
在表单提交期间,shield_middleware 将自动验证 _csrf 令牌,仅允许具有有效 CSRF 令牌的表单提交。
处理异常
当 CSRF 令牌丢失或无效时,Shield 会引发 E_BAD_CSRF_TOKEN 异常。默认情况下,AdonisJS 将捕获该异常并将用户重定向回带有错误闪存消息的表单。
您可以在 edge 模板中按如下方式访问闪存消息。
@error('E_BAD_CSRF_TOKEN')
<p> {{ $message }} </p>
@end
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="Enter your name">
<button type="submit"> Submit </button>
</form>
您还可以按如下方式在全局异常处理器中自行处理 E_BAD_CSRF_TOKEN 异常。
import app from '@adonisjs/core/services/app'
import { errors } from '@adonisjs/shield'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
export default class HttpExceptionHandler extends ExceptionHandler {
async handle(error: unknown, ctx: HttpContext) {
if (error instanceof errors.E_BAD_CSRF_TOKEN) {
return ctx.response
.status(error.status)
.send('Page has expired')
}
return super.handle(error, ctx)
}
}
配置参考
CSRF 守卫的配置存储在 config/shield.ts 文件中。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csrf: {
enabled: true,
exceptRoutes: [],
enableXsrfCookie: true,
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
},
})
export default shieldConfig
-
enabled
-
打开或关闭 CSRF 守卫。
-
exceptRoutes
-
要从 CSRF 保护中豁免的路由模式数组。如果您的应用程序有通过 API 接受表单提交的路由,您可能希望豁免它们。
对于更高级的用例,您可以注册一个函数来动态豁免特定路由。
{exceptRoutes: (ctx) => {// 豁免所有以 /api/ 开头的路由return ctx.request.url().includes('/api/')}} -
enableXsrfCookie
-
启用后,Shield 将 CSRF 令牌存储在名为
XSRF-TOKEN的加密 cookie 中,前端 JavaScript 代码可以读取该 cookie。这允许像 Axios 这样的前端请求库自动读取
XSRF-TOKEN并在进行 Ajax 请求时将其设置为X-XSRF-TOKEN头,而无需服务器渲染的表单。如果您没有以编程方式触发 Ajax 请求,则必须保持
enableXsrfCookie禁用。 -
methods
-
要保护的 HTTP 方法数组。所有提到的方法的传入请求都必须提供有效的 CSRF 令牌。
-
cookieOptions
-
XSRF-TOKENcookie 的配置。查看 cookies 配置以获取可用选项。
定义 CSP 策略
CSP (内容安全策略) 通过定义加载 JavaScript、CSS、字体、图像等的可信来源来保护您的应用程序免受 XSS 攻击。
默认情况下,CSP 守卫是禁用的。但是,我们建议您启用它并在 config/shield.ts 文件中配置策略指令。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
csp: {
enabled: true,
directives: {
// 策略指令放在这里
},
reportOnly: false,
},
})
export default shieldConfig
-
enabled
-
打开或关闭 CSP 守卫。
-
directives
-
配置 CSP 指令。您可以在 https://content-security-policy.com/ 上查看可用指令列表。
const shieldConfig = defineConfig({csp: {enabled: true,directives: {defaultSrc: [`'self'`],scriptSrc: [`'self'`, 'https://cdnjs.cloudflare.com'],fontSrc: [`'self'`, 'https://fonts.googleapis.com']},reportOnly: false,},})export default shieldConfig -
reportOnly
-
启用
reportOnly标志时,CSP 策略将不会阻止资源。相反,它将在使用reportUri指令配置的端点上报告违规行为。const shieldConfig = defineConfig({csp: {enabled: true,directives: {defaultSrc: [`'self'`],reportUri: ['/csp-report']},reportOnly: true,},})此外,注册
csp-report端点以收集违规报告。router.post('/csp-report', async ({ request }) => {const report = request.input('csp-report')})
使用 Nonce
您可以通过在它们上定义 nonce 属性 来允许内联 script 和 style 标签。可以在 Edge 模板中使用 cspNonce 属性访问 nonce 属性的值。
<script nonce="{{ cspNonce }}">
// Inline JavaScript
</script>
<style nonce="{{ cspNonce }}">
/* Inline CSS */
</style>
此外,在指令配置中使用 @nonce 关键字以允许基于 nonce 的内联脚本和样式。
const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@nonce'],
},
},
})
从 Vite 开发服务器加载资产
如果您正在使用 Vite 集成,则可以使用以下 CSP 关键字来允许 Vite 开发服务器提供的资产。
@viteDevUrl将 Vite 开发服务器 URL 添加到允许列表中。@viteHmrUrl将 Vite HMR websocket 服务器 URL 添加到允许列表中。
const shieldConfig = defineConfig({
csp: {
directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
connectSrc: ['@viteHmrUrl']
},
},
})
如果您将 Vite 捆绑输出部署到 CDN 服务器,则必须将 @viteDevUrl 替换为 @viteUrl 关键字,以允许来自开发服务器和 CDN 服务器的资产。
directives: {
defaultSrc: [`'self'`, '@viteDevUrl'],
defaultSrc: [`'self'`, '@viteUrl'],
connectSrc: ['@viteHmrUrl']
},
将 Nonce 添加到 Vite 注入的样式中
目前,Vite 不允许为它在 DOM 中注入的 style 标签定义 nonce 属性。对此有一个 公开的 PR,我们希望它能尽快解决。
配置 HSTS
Strict-Transport-Security (HSTS) 响应头通知浏览器始终使用 HTTPS 加载网站。
您可以使用 config/shield.ts 文件配置头指令。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
hsts: {
enabled: true,
maxAge: '180 days',
includeSubDomains: true,
},
})
-
enabled
-
打开或关闭 hsts 守卫。
-
maxAge
-
定义
max-age属性。该值应该是以秒为单位的数字或基于字符串的时间表达式。{// 记住 10 秒maxAge: 10,}{// 记住 2 天maxAge: '2 days',} -
includeSubDomains
-
定义
includeSubDomains指令以在子域上应用设置。
配置 X-Frame 保护
X-Frame-Options 头用于指示是否允许浏览器渲染嵌入在 iframe、frame、embed 或 object 标签中的网站。
如果您已配置 CSP,则可以使用 frame-ancestors 指令并禁用 xFrame 守卫。
您可以使用 config/shield.ts 文件配置头指令。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
xFrame: {
enabled: true,
action: 'DENY'
},
})
-
enabled
-
打开或关闭 xFrame 守卫。
-
action
-
action属性定义头值。它可以是DENY、SAMEORIGIN或ALLOW-FROM。{action: 'DENY'}在
ALLOW-FROM的情况下,您还必须定义domain属性。{action: 'ALLOW-FROM',domain: 'https://foo.com',}
禁用 MIME 嗅探
X-Content-Type-Options 头指示浏览器遵循 content-type 头,不要通过检查 HTTP 响应的内容来执行 MIME 嗅探。
启用此守卫后,Shield 将为所有 HTTP 响应定义 X-Content-Type-Options: nosniff 头。
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
contentTypeSniffing: {
enabled: true,
},
})