Hono 生产实战 01:安全中间件和接口防护
wxk1991 Lv5

Hono 生产实战 01:安全中间件和接口防护

Hono 项目能跑起来很容易。

但能安全上线,是另一回事。

很多 API 出问题,不是因为业务逻辑多复杂,而是因为最基础的防护没做:

1
2
3
4
5
6
CORS 随便放开
请求体没有大小限制
错误堆栈直接返回给前端
没有安全响应头
登录接口没有限流
管理接口只靠前端隐藏按钮

Hono 很轻,所以它不会替你默认打开一整套企业级安全策略。你要自己把生产 API 的基础防线搭起来。

这篇文章讲 Hono 生产环境最应该先做的安全中间件和接口防护。

技术栈快照时间:2026-06-23。Hono 和 Cloudflare 相关中间件会持续更新,具体 API 以官方文档为准。


一、先给安全基线

一个生产 Hono API,至少要考虑:

1
2
3
4
5
6
7
8
9
secure headers
CORS 白名单
body limit
统一错误响应
鉴权中间件
权限中间件
基础限流
请求 ID
日志脱敏

如果是浏览器前端调用,还要额外考虑:

1
2
3
4
Cookie SameSite
CSRF
Authorization header
HTTPS

不要把这些放到上线前最后一天再补。

安全策略越晚加,越容易破坏已有接口。


二、secureHeaders

Hono 内置 secureHeaders middleware,可以帮你加一组常见安全响应头。

1
2
3
4
5
6
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'

const app = new Hono()

app.use('*', secureHeaders())

这类 header 不是银弹,但它能减少一些基础风险,比如 MIME sniffing、iframe 嵌入、过宽的权限策略等。

如果你有特殊需求,可以定制:

1
2
3
4
app.use('*', secureHeaders({
xFrameOptions: 'DENY',
xContentTypeOptions: 'nosniff',
}))

不要指望它解决所有安全问题。

它只是第一层地基。


三、CORS 不要无脑星号

本地开发时很多人喜欢:

1
app.use('*', cors())

这会让开发很爽,但生产环境不要无脑放开。

更推荐按环境配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { cors } from 'hono/cors'

type Bindings = {
CORS_ORIGIN: string
}

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

app.use('/api/*', async (c, next) => {
const corsHandler = cors({
origin: c.env.CORS_ORIGIN,
allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true,
})

return corsHandler(c, next)
})

如果你不用 cookie,可以不打开 credentials

如果你打开了 credentials: true,就不要使用 origin: '*'。浏览器不会接受这种组合。

CORS 的目标不是“让所有请求都过”,而是只允许可信前端域名访问。


四、限制请求体大小

没有 body limit 的接口,很容易被大请求拖垮。

Hono 内置 bodyLimit

1
2
3
4
5
6
7
8
9
10
11
12
13
import { bodyLimit } from 'hono/body-limit'

app.use('/api/*', bodyLimit({
maxSize: 1024 * 1024,
onError: (c) => {
return c.json({
error: {
code: 'PAYLOAD_TOO_LARGE',
message: '请求体太大',
},
}, 413)
},
}))

普通 JSON API,1MB 通常已经很大。

文件上传接口应该单独配置,不要让所有接口都接受大 body。

比如:

1
2
3
app.use('/api/upload/*', bodyLimit({
maxSize: 10 * 1024 * 1024,
}))

接口粒度越细,越不容易误伤。


五、统一错误响应

生产环境不要把未知错误堆栈返回给前端。

统一写 onError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.onError((err, c) => {
const requestId = c.get('requestId')

console.error('unhandled_error', {
requestId,
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
})

return c.json({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: '服务器内部错误',
},
requestId,
}, 500)
})

前端拿到 requestId,就能把问题反馈给后端。

后端通过日志查堆栈。

这比直接把 stack 暴露给用户安全得多。


六、鉴权和授权分开

鉴权是确认:

1
你是谁

授权是确认:

1
你能做什么

不要混在一起。

鉴权中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.use('/api/private/*', async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')

if (!token) {
return c.json({
error: {
code: 'UNAUTHORIZED',
message: '请先登录',
},
}, 401)
}

const user = await verifyToken(token)
c.set('user', user)

await next()
})

权限中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function requireRole(role: 'admin' | 'user') {
return async (c, next) => {
const user = c.get('user')

if (user.role !== role) {
return c.json({
error: {
code: 'FORBIDDEN',
message: '没有权限',
},
}, 403)
}

await next()
}
}

前端隐藏按钮只是体验,后端权限校验才是安全。


七、登录和敏感接口要限流

限流不是所有接口都同一套。

最应该限流的是:

1
2
3
4
5
6
7
8
登录
注册
验证码
密码重置
发送邮件
文件上传
Webhook
高成本查询

Cloudflare 平台可以用 WAF / Rate Limiting 这类边缘能力。

如果你想在 Hono 里做一层简单限流,可以用 KV、Redis、Durable Object 或外部服务。

一个概念版 middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function rateLimit(c, next) {
const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
const key = `rate:${ip}:${Math.floor(Date.now() / 60_000)}`

const current = Number(await c.env.KV.get(key) ?? 0)

if (current >= 20) {
return c.json({
error: {
code: 'RATE_LIMITED',
message: '请求太频繁',
},
}, 429)
}

await c.env.KV.put(key, String(current + 1), {
expirationTtl: 120,
})

await next()
}

注意,这只是基础窗口计数。

高安全场景要考虑并发竞态、全局一致性和平台级限流。


八、日志不要泄漏敏感信息

日志里不要打印:

1
2
3
4
5
6
7
8
9
密码
access token
refresh token
cookie
身份证
手机号完整值
银行卡
私钥
验证码

错误日志也要脱敏。

比如:

1
2
3
4
console.info('login_failed', {
requestId: c.get('requestId'),
email: maskEmail(input.email),
})

日志是排查工具,不是数据仓库。


九、推荐中间件顺序

一个基础顺序:

1
2
3
4
5
6
7
8
9
app.use('*', requestId())
app.use('*', secureHeaders())
app.use('/api/*', corsConfig())
app.use('/api/*', bodyLimitConfig())
app.use('/api/private/*', auth())
app.use('/api/admin/*', requireRole('admin'))

app.notFound(notFoundHandler)
app.onError(errorHandler)

顺序很重要。

比如 CORS 要在 route 前面。

body limit 要在解析 body 前面。

鉴权要在私有接口 route 前面。


十、最后的建议

Hono 的轻量是优势,但生产安全不能轻。

我建议每个 Hono API 项目一开始就把这些做好:

1
2
3
4
5
6
7
8
安全响应头
CORS 白名单
请求体大小限制
统一错误响应
鉴权和授权分层
敏感接口限流
日志脱敏
requestId

不要等接口写完再补安全。

安全不是最后一层装饰,而是 API 设计的一部分。


参考资料