保护服务端渲染应用程序
如果您正在使用 AdonisJS 创建服务端渲染应用程序,那么您必须使用 @adonisjs/shield 包来保护您的应用程序免受常见的 Web 攻击,如 CSRF、XSS、内容嗅探 等。
该包已预配置在 web 入门套件中。但是,您可以按如下方式手动安装和配置该包。
TIP
@adonisjs/shield 包对 @adonisjs/session 包有对等依赖,因此请确保先配置会话包。
node ace add @adonisjs/shield:::disclosure
使用检测到的包管理器安装
@adonisjs/shield包。在
adonisrc.ts文件中注册以下服务提供器。ts{ providers: [ // ...其他提供器 () => import('@adonisjs/shield/shield_provider'), ] }创建
config/shield.ts文件。在
start/kernel.ts文件中注册以下中间件。tsrouter.use([() => import('@adonisjs/shield/shield_middleware')])
:::
CSRF 保护
CSRF(跨站请求伪造)是一种攻击,恶意网站诱骗您的 Web 应用程序用户在未经其明确同意的情况下执行表单提交。
为了防止 CSRF 攻击,您应该定义一个隐藏的输入字段,其中包含只有您的网站才能生成和验证的 CSRF 令牌值。因此,由恶意网站触发的表单提交将失败。
保护表单
配置 @adonisjs/shield 包后,所有没有 CSRF 令牌的表单提交将自动失败。因此,您必须使用 csrfField edge 助手来定义一个包含 CSRF 令牌的隐藏输入字段。
:::caption{for="info"} Edge 助手 :::
<form method="POST" action="/">
// highlight-start
{{ csrfField() }}
// highlight-end
<input type="name" name="name" placeholder="输入您的名字">
<button type="submit"> 提交 </button>
</form>:::caption{for="info"} 输出 HTML :::
<form method="POST" action="/">
// highlight-start
<input type="hidden" name="_csrf" value="Q9ghWSf0-3FD9eCiu5YxvKaxLEZ6F_K4DL8o"/>
// highlight-end
<input type="name" name="name" placeholder="输入您的名字"/>
<button type="submit">提交</button>
</form>在表单提交期间,shield_middleware 将自动验证 _csrf 令牌,只允许具有有效 CSRF 令牌的表单提交。
处理异常
当 CSRF 令牌缺失或无效时,Shield 会引发 E_BAD_CSRF_TOKEN 异常。默认情况下,AdonisJS 将捕获异常并将用户重定向回表单,并显示错误闪存消息。
您可以按如下方式在 edge 模板中访问闪存消息。
// highlight-start
@error('E_BAD_CSRF_TOKEN')
<p> {{ $message }} </p>
@end
// highlight-end
<form method="POST" action="/">
{{ csrfField() }}
<input type="name" name="name" placeholder="输入您的名字">
<button type="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) {
// highlight-start
if (error instanceof errors.E_BAD_CSRF_TOKEN) {
return ctx.response
.status(error.status)
.send('页面已过期')
}
// highlight-end
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 shieldConfigenabled
打开或关闭 CSRF 守卫。
exceptRoutes
要从 CSRF 保护中豁免的路由模式数组。如果您的应用程序有通过 API 接受表单提交的路由,您可能需要豁免它们。
对于更高级的用例,您可以注册一个函数来动态豁免特定路由。
ts{ exceptRoutes: (ctx) => { // 豁免所有以 /api/ 开头的路由 return ctx.request.url().includes('/api/') } }enableXsrfCookie
启用后,Shield 会将 CSRF 令牌存储在名为
XSRF-TOKEN的加密 cookie 中,前端 JavaScript 代码可以读取它。这允许像 Axios 这样的前端请求库在进行 Ajax 请求时自动读取
XSRF-TOKEN并将其设置为X-XSRF-TOKEN头,而无需服务端渲染的表单。如果您没有以编程方式触发 Ajax 请求,您必须禁用
enableXsrfCookie。methods
要保护的 HTTP 方法数组。所有提到的方法的传入请求都必须提供有效的 CSRF 令牌。
cookieOptions
XSRF-TOKENcookie 的配置。查看 cookie 配置了解可用选项。
定义 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 shieldConfigenabled
打开或关闭 CSP 守卫。
directives
配置 CSP 指令。您可以在 https://content-security-policy.com/ 查看可用指令列表
tsconst shieldConfig = defineConfig({ csp: { enabled: true, // highlight-start directives: { defaultSrc: [`'self'`], scriptSrc: [`'self'`, 'https://cdnjs.cloudflare.com'], fontSrc: [`'self'`, 'https://fonts.googleapis.com'] }, // highlight-end reportOnly: false, }, }) export default shieldConfigreportOnly
启用
reportOnly标志后,CSP 策略不会阻止资源。相反,它会在使用reportUri指令配置的端点上报告违规。tsconst shieldConfig = defineConfig({ csp: { enabled: true, directives: { defaultSrc: [`'self'`], // highlight-start reportUri: ['/csp-report'] // highlight-end }, // highlight-start reportOnly: true, // highlight-end }, })此外,注册
csp-report端点以收集违规报告。tsrouter.post('/csp-report', async ({ request }) => { const report = request.input('csp-report') })
使用 Nonce
您可以通过在内联 script 和 style 标签上定义 nonce 属性来允许它们。nonce 属性的值可以在 Edge 模板中使用 cspNonce 属性访问。
<script nonce="{{ cspNonce }}">
// 内联 JavaScript
</script>
<style nonce="{{ cspNonce }}">
/* 内联 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: {
// delete-start
defaultSrc: [`'self'`, '@viteDevUrl'],
// delete-end
// insert-start
defaultSrc: [`'self'`, '@viteUrl'],
// insert-end
connectSrc: ['@viteHmrUrl']
},为 Vite 注入的样式添加 Nonce
目前,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属性。值应该是以秒为单位的数字或基于字符串的时间表达式。ts{ // 记住 10 秒 maxAge: 10, }ts{ // 记住 2 天 maxAge: '2 days', }includeSubDomains
定义
includeSubDomains指令以在子域上应用设置。
配置 X-Frame 保护
X-Frame-Options 头用于指示是否允许浏览器在 iframe、frame、embed 或 object 标签中嵌入网站进行渲染。
TIP
如果您已配置 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。ts{ action: 'DENY' }在
ALLOW-FROM的情况下,您还必须定义domain属性。ts{ 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,
},
})