Skip to content

安全加固:secureHeaderscsrfxss 防护中间件

设置 Content-Security-PolicyX-Frame-Options

在现代 Web 应用中,安全必须是默认配置,而非事后补救。Hono 提供了轻量、高效的中间件生态,可轻松集成关键安全防护,抵御常见攻击如 XSS、CSRF、点击劫持等。

本文将介绍如何通过 Hono 中间件实现全面的安全加固。

一、核心安全威胁与对策

威胁风险防护措施
XSS(跨站脚本)注入恶意脚本窃取数据CSP、输入过滤、转义
CSRF(跨站请求伪造)伪造用户请求执行操作CSRF Token、SameSite Cookie
点击劫持(Clickjacking)诱骗用户点击隐藏元素X-Frame-Optionsframe-ancestors
MIME 类型嗅探执行非预期内容X-Content-Type-Options: nosniff
信息泄露暴露服务器版本X-Powered-By 移除

二、1. secureHeaders:设置基础安全头

Hono 内置 secureHeaders 中间件,自动设置多个关键安全头。

安装
bash
npm install @hono/hono
启用默认安全头
ts
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'

const app = new Hono()

// 启用基础安全头
app.use('*', secureHeaders())

// 或自定义配置
app.use('*', secureHeaders({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "trusted-cdn.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "cdn.example.com"],
      connectSrc: ["'self'", "api.example.com"],
      frameAncestors: ["'none'"], // 禁止嵌入 iframe
    },
  },
  xFrameOptions: 'DENY', // 可选:SAMEORIGIN
  xContentTypeOptions: 'nosniff',
  referrerPolicy: 'strict-origin-when-cross-origin',
  permissionsPolicy: {
    camera: [],
    microphone: [],
    geolocation: ['self'],
  }
}))
生成的头部示例:
http
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' trusted-cdn.com; ...
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(self)

secureHeaders 是安全加固的第一步,应始终启用。

三、2. csrf 防护:防止跨站请求伪造

CSRF 攻击利用用户登录状态,伪造请求执行非预期操作(如转账、改密码)。

使用 csrf 中间件
ts
import { csrf } from 'hono/csrf'

// 仅对表单提交等敏感路由启用
app.use('/api/*', csrf())

// 示例:受保护的 API
app.post('/api/update-profile', async (c) => {
  const data = await c.req.json()
  // 此处已通过 CSRF 校验
  return c.json({ success: true })
})
工作原理:
  • 中间件在响应中设置 Set-Cookie: _csrf=xxx
  • 前端需在请求头中携带 CSRF-Token: xxx
  • 服务端校验 token 是否匹配。
前端配合(Fetch):
ts
// 获取 CSRF Token(首次访问时 cookie 已设置)
const token = document.cookie
  .split('; ')
  .find(row => row.startsWith('_csrf='))
  ?.split('=')[1]

fetch('/api/update-profile', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'CSRF-Token': token!
  },
  body: JSON.stringify(data)
})

适用于传统表单或非同源 API。

四、3. xss 防护:输入过滤与转义

Hono 无内置 XSS 过滤中间件,但可通过自定义中间件实现。

自定义 XSS 防护中间件
ts
// middleware/xss.ts
import { XSSOptions } from 'xss'
import xss from 'xss'

export const xssProtection = (options?: XSSOptions) => {
  const defaultOptions = {
    whiteList: {
      // 仅允许基本标签
      b: [],
      i: [],
      em: [],
      strong: [],
      p: [],
      br: [],
      a: ['href', 'target']
    },
    stripIgnoreTag: true, // 移除不支持的标签
    ...options
  }

  return async (c: any, next: () => Promise<any>) => {
    const originalParse = c.req.parseBody

    // 包装 parseBody,自动净化表单数据
    c.req.parseBody = async () => {
      const body = await originalParse.call(c.req)
      const cleanBody: any = {}

      for (const [key, value] of Object.entries(body)) {
        if (typeof value === 'string') {
          cleanBody[key] = xss(value, defaultOptions)
        } else {
          cleanBody[key] = value
        }
      }

      return cleanBody
    }

    // 包装 json,自动净化
    const originalJson = c.req.json
    c.req.json = async () => {
      const data = await originalJson.call(c.req)
      return sanitizeObject(data, defaultOptions)
    }

    await next()
  }
}

// 递归净化对象
function sanitizeObject(obj: any, options: XSSOptions) {
  if (typeof obj !== 'object' || obj === null) return obj

  if (Array.isArray(obj)) {
    return obj.map(item => sanitizeObject(item, options))
  }

  const result: any = {}
  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === 'string') {
      result[key] = xss(value, options)
    } else {
      result[key] = sanitizeObject(value, options)
    }
  }
  return result
}
使用:
ts
import { xssProtection } from './middleware/xss'

app.use('/api/user/*', xssProtection())

五、其他安全实践

ts
c.cookie('session', token, {
  httpOnly: true,   // 防止 JS 访问
  secure: true,     // HTTPS only
  sameSite: 'lax',  // 防 CSRF
  maxAge: 86400
})
2. 速率限制(防暴力破解)
ts
import { ratelimit } from '@hono/ratelimit'
import { Redis } from '@upstash/redis'

const redis = new Redis({ url: '', token: '' })

app.use('/login', ratelimit({
  window: '10 m',
  limit: 5,
  keyPrefix: 'login',
  async store(request) {
    return {
      async increment(key) {
        const value = await redis.incr(key)
        await redis.expire(key, 600)
        return { value, limit: 5, reset: Date.now() + 600_000 }
      }
    }
  }
}))
3. 移除 X-Powered-By
ts
app.use('*', (c, next) => {
  c.res.headers.delete('X-Powered-By')
  return next()
})

六、最终安全加固结构

ts
const app = new Hono()

// 1. 基础安全头
app.use('*', secureHeaders({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      frameAncestors: ["'none'"]
    }
  },
  xFrameOptions: 'DENY'
}))

// 2. CSRF 保护(敏感路由)
app.use('/api/admin/*', csrf())

// 3. XSS 输入净化
app.use('/api/user/*', xssProtection())

// 4. 速率限制
app.use('/login', rateLimitMiddleware)

// 5. 你的业务路由
app.post('/api/user/profile', (c) => {})

export default app

七、验证安全头

部署后使用 SecurityHeaders.com 扫描,确保评级为 A+

八、总结

中间件作用推荐等级
secureHeaders设置 CSP、X-Frame-Options 等✅✅✅ 必用
csrf防跨站请求伪造✅✅ 敏感操作必用
自定义 xssProtection净化输入✅ 处理用户内容时使用
ratelimit防暴力破解✅ 登录/注册必用

安全是持续过程。通过 Hono 的中间件机制,你可以在不影响业务逻辑的前提下,轻松构建纵深防御体系,显著提升应用安全性。