AdonisJS v7 已完成功能开发,正在进行最终验证。 了解更多

保护 SSR 应用

保护服务器渲染的应用程序 (Securing server-rendered applications)

如果您正在使用 AdonisJS 创建服务器渲染的应用程序,那么您必须使用 @adonisjs/shield 软件包来保护您的应用程序免受常见的 Web 攻击,如 CSRFXSS内容嗅探等。

该软件包已在 web starter kit 中预先配置。但是,您可以按如下方式手动安装和配置该软件包。

@adonisjs/shield 软件包与 @adonisjs/session 软件包具有对等依赖关系,因此请确保首先配置会话软件包

node ace add @adonisjs/shield
  1. 使用检测到的包管理器安装 @adonisjs/shield 软件包。

  2. adonisrc.ts 文件中注册以下服务提供者。

    {
    providers: [
    // ...其他提供者
    () => import('@adonisjs/shield/shield_provider'),
    ]
    }
  3. 创建 config/shield.ts 文件。

  4. 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-TOKEN cookie 的配置。查看 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 属性 来允许内联 scriptstyle 标签。可以在 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 头用于指示是否允许浏览器渲染嵌入在 iframeframeembedobject 标签中的网站。

如果您已配置 CSP,则可以使用 frame-ancestors 指令并禁用 xFrame 守卫。

您可以使用 config/shield.ts 文件配置头指令。

import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
xFrame: {
enabled: true,
action: 'DENY'
},
})

enabled

打开或关闭 xFrame 守卫。

action

action 属性定义头值。它可以是 DENYSAMEORIGINALLOW-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,
},
})