深色模式
Goa 中间件与鉴权
概述
Goa 里和“请求处理链”有关的东西,不只有一种。官方文档把它分成三类:Goa interceptor、HTTP middleware 和 gRPC interceptor。对中小型 HTTP 项目来说,真正最常用的是前两种,再加上 Goa 自己的安全 DSL。
如果这几层职责没分清,项目很快会出现一种局面:JWT 解析写在每个方法里,日志和请求 ID 写进业务代码,权限判断到处复制。代码不一定长,但会很黏。把每类问题放到合适的层,后面会轻很多。
先分清三种工具
Goa Interceptor
Goa interceptor 更贴近领域类型,适合处理这些事:
- 请求进入业务前做统一补充或校验
- 对业务输入做通用约束
- 给多个方法统一塞上下文信息
HTTP Middleware
HTTP middleware 还是标准 http.Handler 思路,更适合这些和协议强相关的事情:
- 日志
request_idpanic recovery- CORS
- 压缩
- 限流
Security DSL
鉴权在 Goa 里通常不直接写成一个普通 middleware,而是通过安全 DSL 描述“这个接口需要什么认证方案”,再实现对应的认证函数。
中小项目里,一个实用的分工通常是:
- 日志、恢复、跨域:HTTP middleware
- JWT、API Key、Basic Auth:Goa Security
- 通用业务增强:必要时用 Goa interceptor
JWT 鉴权怎么写
先定义 JWT 安全方案:
go
var JWTAuth = JWTSecurity("jwt", func() {
Description("JWT authentication")
Scope("api:read", "读取权限")
Scope("api:write", "写入权限")
})再把它应用到方法上:
go
Method("show", func() {
Security(JWTAuth, func() {
Scope("api:read")
})
Payload(func() {
TokenField(1, "token", String)
Field(2, "id", UInt64)
Required("token", "id")
})
Result(User)
Error("unauthorized", ErrorResult)
Error("forbidden", ErrorResult)
HTTP(func() {
GET("/users/{id}")
Header("token:Authorization")
Response("unauthorized", StatusUnauthorized)
Response("forbidden", StatusForbidden)
})
})这里 DSL 表达的是两件事:
- 这个方法需要 JWT
- 这个方法至少需要
api:read这个 scope
Goa 会基于这些定义生成相应的认证接线代码和文档。
认证函数一般怎么实现
Goa 文档说明,生成代码会要求你实现对应的认证函数。JWT 的典型签名类似这样:
go
func (s *AuthService) JWTAuth(ctx context.Context, token string, scheme *security.JWTScheme) (context.Context, error) {
claims, err := verifyJWT(token)
if err != nil {
return ctx, ErrUnauthorized
}
if !hasScopes(claims, scheme.RequiredScopes) {
return ctx, ErrForbidden
}
ctx = context.WithValue(ctx, userIDKey{}, claims.Subject)
return ctx, nil
}这里比较关键的是返回值:
- 返回错误,表示鉴权失败
- 返回新的
context.Context,表示把认证后的用户信息继续传给后面的业务逻辑
所以对中小项目来说,把用户 ID、角色、租户信息塞进 context,通常已经够用。
中间件放在哪里更合适
中小型 HTTP 项目,一般可以先挂这几类 middleware:
- 请求日志
request_idpanic recovery- CORS
而这些东西不应该直接写进 service 方法。因为它们和具体业务无关,放在 HTTP middleware 层复用成本最低。
如果后面有统一的审计字段补充、公共参数修正、通用业务校验,再考虑上 Goa interceptor。不要一开始就把所有横切逻辑都往 interceptor 里堆,普通 middleware 能解决的事,没必要多上一层抽象。
中小项目里怎么选最省事
公共日志和异常恢复
优先用 HTTP middleware。它们本来就是 HTTP 层问题,不需要知道 Goa 的业务类型。
用户认证
优先用 Goa Security。这样接口契约、OpenAPI 文档和认证要求是一致的。
细粒度权限判断
如果只是个别方法有额外规则,直接放在 service 里就可以。如果很多方法都重复同一套判断,再考虑抽到 interceptor 或更明确的授权组件里。
常见问题
把 JWT 解析写进每个 service 方法
这会让业务层被认证细节污染。认证应该尽量在进入业务之前完成。
用 interceptor 做 CORS 或日志
这类问题本来就属于 HTTP 层,放到 middleware 更自然,也更符合标准库生态。
一上来就在 API 级别强制所有方法鉴权
很多中小项目都会有登录、健康检查、公开配置这类无需鉴权的方法。安全方案先从 service 级或 method 级开始,往往更灵活。
