Skip to content

Secure Headers 中间件

Secure Headers 中间件简化了安全头部的设置。部分灵感来源于 Helmet 的功能,它允许您控制特定安全头部的激活和停用。

导入

ts
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'

用法

默认情况下,您可以使用最佳设置。

ts
const app = new Hono()
app.use(secureHeaders())

您可以通过将它们设置为 false 来抑制不必要的头部。

ts
const app = new Hono()
app.use(
  '*',
  secureHeaders({
    xFrameOptions: false,
    xXssProtection: false,
  })
)

您可以使用字符串覆盖默认的头部值。

ts
const app = new Hono()
app.use(
  '*',
  secureHeaders({
    strictTransportSecurity:
      'max-age=63072000; includeSubDomains; preload',
    xFrameOptions: 'DENY',
    xXssProtection: '1',
  })
)

支持的选项

每个选项对应以下头部键值对。

选项头部默认
-X-Powered-By(删除头部)启用
contentSecurityPolicy内容安全策略用法:设置内容安全策略未设置
contentSecurityPolicyReportOnly内容安全策略仅报告用法:设置内容安全策略未设置
trustedTypes可信类型用法:设置内容安全策略未设置
requireTrustedTypesFor要求可信类型用于用法:设置内容安全策略未设置
crossOriginEmbedderPolicy跨源嵌入者策略require-corp禁用
crossOriginResourcePolicy跨源资源策略same-origin启用
crossOriginOpenerPolicy跨源打开者策略same-origin启用
originAgentCluster源代理集群?1启用
referrerPolicy引荐来源策略no-referrer启用
reportingEndpoints报告端点用法:设置内容安全策略未设置
reportTo报告至用法:设置内容安全策略未设置
strictTransportSecurity严格传输安全max-age=15552000; includeSubDomains启用
xContentTypeOptionsX-Content-Type-Optionsnosniff启用
xDnsPrefetchControlX-DNS-Prefetch-Controloff启用
xDownloadOptionsX-Download-Optionsnoopen启用
xFrameOptionsX-Frame-OptionsSAMEORIGIN启用
xPermittedCrossDomainPoliciesX-Permitted-Cross-Domain-Policiesnone启用
xXssProtectionX-XSS-Protection0启用
permissionPolicy权限策略用法:设置权限策略未设置

中间件冲突

在处理操作相同头部的中间件时,请注意指定顺序。

在这种情况下,Secure-headers 生效,x-powered-by 被移除:

ts
const app = new Hono()
app.use(secureHeaders())
app.use(poweredBy())

在这种情况下,Powered-By 生效,x-powered-by 被添加:

ts
const app = new Hono()
app.use(poweredBy())
app.use(secureHeaders())

设置内容安全策略

ts
const app = new Hono()
app.use(
  '/test',
  secureHeaders({
    reportingEndpoints: [
      {
        name: 'endpoint-1',
        url: 'https://example.com/reports',
      },
    ],
    // -- 或者另一种方式
    // reportTo: [
    //   {
    //     group: 'endpoint-1',
    //     max_age: 10886400,
    //     endpoints: [{ url: 'https://example.com/reports' }],
    //   },
    // ],
    contentSecurityPolicy: {
      defaultSrc: ["'self'"],
      baseUri: ["'self'"],
      childSrc: ["'self'"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'", 'https:', 'data:'],
      formAction: ["'self'"],
      frameAncestors: ["'self'"],
      frameSrc: ["'self'"],
      imgSrc: ["'self'", 'data:'],
      manifestSrc: ["'self'"],
      mediaSrc: ["'self'"],
      objectSrc: ["'none'"],
      reportTo: 'endpoint-1',
      reportUri: '/csp-report',
      sandbox: ['allow-same-origin', 'allow-scripts'],
      scriptSrc: ["'self'"],
      scriptSrcAttr: ["'none'"],
      scriptSrcElem: ["'self'"],
      styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
      styleSrcAttr: ['none'],
      styleSrcElem: ["'self'", 'https:', "'unsafe-inline'"],
      upgradeInsecureRequests: [],
      workerSrc: ["'self'"],
    },
  })
)

nonce 属性

您可以通过将 hono/secure-headers 导出的 NONCE 添加到 scriptSrcstyleSrc,从而为 scriptstyle 元素添加 nonce 属性

tsx
import { secureHeaders, NONCE } from 'hono/secure-headers'
import type { SecureHeadersVariables } from 'hono/secure-headers'

// 指定变量类型以推断 `c.get('secureHeadersNonce')`:
type Variables = SecureHeadersVariables

const app = new Hono<{ Variables: Variables }>()

// 将预定义的 nonce 值设置到 `scriptSrc`:
app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [NONCE, 'https://allowed1.example.com'],
    },
  })
)

// 从 `c.get('secureHeadersNonce')` 获取值:
app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        {/** 内容 */}
        <script
          src='/js/client.js'
          nonce={c.get('secureHeadersNonce')}
        />
      </body>
    </html>
  )
})

如果您想自己生成 nonce 值,也可以按以下方式指定一个函数:

tsx
const app = new Hono<{
  Variables: { myNonce: string }
}>()

const myNonceGenerator: ContentSecurityPolicyOptionHandler = (c) => {
  // 此函数在每次请求时被调用。
  const nonce = Math.random().toString(36).slice(2)
  c.set('myNonce', nonce)
  return `'nonce-${nonce}'`
}

app.get(
  '*',
  secureHeaders({
    contentSecurityPolicy: {
      scriptSrc: [myNonceGenerator, 'https://allowed1.example.com'],
    },
  })
)

app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        {/** 内容 */}
        <script src='/js/client.js' nonce={c.get('myNonce')} />
      </body>
    </html>
  )
})

设置 Permission-Policy

Permission-Policy 标头允许您控制浏览器中可以使用哪些功能和 API。以下是如何设置它的示例:

ts
const app = new Hono()
app.use(
  '*',
  secureHeaders({
    permissionsPolicy: {
      fullscreen: ['self'], // 全屏=(self)
      bluetooth: ['none'], // 蓝牙=(none)
      payment: ['self', 'https://example.com'], // 支付=(self "https://example.com")
      syncXhr: [], // 同步 XMLHttpRequest=()
      camera: false, // 摄像头=none
      microphone: true, // 麦克风=*
      geolocation: ['*'], // 地理位置=*
      usb: ['self', 'https://a.example.com', 'https://b.example.com'], // USB=(self "https://a.example.com" "https://b.example.com")
      accelerometer: ['https://*.example.com'], // 加速度计=("https://*.example.com")
      gyroscope: ['src'], // 陀螺仪=(src)
      magnetometer: [
        'https://a.example.com',
        'https://b.example.com',
      ], // 磁力计=("https://a.example.com" "https://b.example.com")
    },
  })
)