凭证体系与 Token 鉴权机制¶
除了网络传输层的 TLS 加密,分布式系统中更细粒度的控制来自于用户与租户级别的身份凭证(Credentials)与授权(Authorization)。
1. gRPC 凭证分层模型¶
gRPC 将安全体系清晰划分为两个独立层级:
graph TD
subgraph ChannelCreds ["1. 通道凭证 (Channel Credentials)"]
CC1["TLS / mTLS / Insecure"]
CC2["保障 TCP 连接级别的保密性与节点身份合法性"]
end
subgraph CallCreds ["2. 调用凭证 (Call Credentials)"]
CA1["OAuth2 Token / JWT / API Key"]
CA2["伴随每个 RPC 独立传递,用于标识操作者身份"]
end
subgraph Composite ["复合凭证 (Composite Credentials)"]
Comp["结合 ChannelCreds + CallCreds<br/>(例如:在 TLS 安全信道上安全传递 Bearer Token)"]
end
ChannelCreds --> Composite
CallCreds --> Composite
[!CAUTION] 安全防护铁律:
PerRPCCredentials默认强制要求RequireTransportSecurity() == true。如果底层不是 TLS 加密信道,gRPC 会直接拒绝发送 Token,防止核心密码凭据在公网明文泄漏。
2. 客户端:实现自定义 PerRPCCredentials¶
在 Go 中,只需实现 credentials.PerRPCCredentials 接口:
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// 1. 定义 Token 凭据对象
type TokenAuth struct {
Token string
}
// 2. 实现 GetRequestMetadata: 在发起 RPC 时自动注入 Metadata
func (t TokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
return map[string]string{
"authorization": "Bearer " + t.Token,
}, nil
}
// 3. 声明是否强制要求 TLS 加密
func (t TokenAuth) RequireTransportSecurity() bool {
return true // 生产环境必须返回 true
}
func main() {
// 加载 TLS 凭据
tlsCreds, _ := credentials.NewClientTLSFromFile("ca-cert.pem", "localhost")
// 组合 TLS 与 Token 认证
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(tlsCreds),
grpc.WithPerRPCCredentials(TokenAuth{Token: "my-jwt-secret-token"}),
)
if err != nil {
panic(err)
}
defer conn.Close()
}
3. 服务端:基于拦截器的 JWT 鉴权与 RBAC 权限控制¶
服务端通过全局 Unary 拦截器拦截所有调用,统一解析 JWT 并执行基于角色的访问控制(RBAC):
package main
import (
"context"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// 定义基于方法的白名单或权限表
var methodPermissions = map[string]string{
"/order.v1.OrderService/GetOrder": "user",
"/order.v1.OrderService/DeleteOrder": "admin",
}
func AuthInterceptor(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
// 1. 提取请求头中的 Metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "缺少元数据头部")
}
authHeaders := md.Get("authorization")
if len(authHeaders) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "缺少 authorization Token")
}
rawToken := authHeaders[0]
if !strings.HasPrefix(rawToken, "Bearer ") {
return nil, status.Errorf(codes.Unauthenticated, "Token 格式非法,必须为 Bearer 格式")
}
tokenString := strings.TrimPrefix(rawToken, "Bearer ")
// 2. 校验 JWT 并解析出 Claims
claims, err := parseAndVerifyJWT(tokenString)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "Token 验证失效: %v", err)
}
// 3. RBAC 细粒度权限校验
requiredRole, exists := methodPermissions[info.FullMethod]
if exists && claims.Role != requiredRole && claims.Role != "super_admin" {
return nil, status.Errorf(codes.PermissionDenied, "权限不足: 接口需要 [%s] 角色", requiredRole)
}
// 4. 将解析出的身份上下文绑定到 Context,传递给业务 Handler
newCtx := context.WithValue(ctx, "user_id", claims.UserID)
return handler(newCtx, req)
}
生产治理建议¶
- 公网边界网关鉴权:通常由 API Gateway(如 Envoy、APISIX)完成外部 JWT 解析与验签,向内部微服务转发时转换为带有数字签名的内部用户 Header,从而减轻内部各微服务重复验签的 CPU 开销。
- 服务间相互调用授权(mTLS + SPIFFE/SPIRE):微服务与微服务之间的身份准入依靠 mTLS 证书中的 SPIFFE ID,而不用传用户密码,实现真正解耦。