跳到主要内容

安全认证

本章介绍 gRPC 的安全机制,包括 TLS/SSL 加密、认证方式和授权控制。

安全概述

TLS 加密

生成证书

# 创建 CA 私钥
openssl genrsa -out ca.key 4096

# 创建 CA 证书
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=MyCA"

# 创建服务端私钥
openssl genrsa -out server.key 4096

# 创建服务端 CSR
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=localhost"

# 使用 CA 签发服务端证书
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt

# 创建客户端私钥和证书(用于 mTLS)
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=client"
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt

服务端 TLS(单向)

服务端使用证书,客户端验证服务端身份:

import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func main() {
// 加载服务端证书
creds, err := credentials.NewServerTLSFromFile(
"server.crt", // 证书文件
"server.key", // 私钥文件
)
if err != nil {
log.Fatalf("加载证书失败: %v", err)
}

// 创建带 TLS 的服务器
s := grpc.NewServer(grpc.Creds(creds))

// 注册服务...
pb.RegisterGreeterServer(s, &server{})

lis, _ := net.Listen("tcp", ":50051")
s.Serve(lis)
}

客户端

func main() {
// 加载 CA 证书验证服务端
creds, err := credentials.NewClientTLSFromFile(
"ca.crt", // CA 证书
"localhost", // 服务器域名(需与证书 CN 匹配)
)
if err != nil {
log.Fatalf("加载证书失败: %v", err)
}

// 创建带 TLS 的连接
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(creds),
)
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()

client := pb.NewGreeterClient(conn)
// ...
}

双向 TLS(mTLS)

双方互相验证:

import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"google.golang.org/grpc/credentials"
)

// 服务端配置
func serverTLS() credentials.TransportCredentials {
// 加载 CA 证书(用于验证客户端)
caCert, _ := ioutil.ReadFile("ca.crt")
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)

// 配置 TLS
config := &tls.Config{
Certificates: []tls.Certificate{
loadCert("server.crt", "server.key"),
},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: caPool,
}

return credentials.NewTLS(config)
}

// 客户端配置
func clientTLS() credentials.TransportCredentials {
caCert, _ := ioutil.ReadFile("ca.crt")
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)

config := &tls.Config{
Certificates: []tls.Certificate{
loadCert("client.crt", "client.key"),
},
RootCAs: caPool,
}

return credentials.NewTLS(config)
}

func loadCert(certFile, keyFile string) tls.Certificate {
cert, _ := tls.LoadX509KeyPair(certFile, keyFile)
return cert
}

Token 认证

拦截器实现

// 认证拦截器
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 跳过不需要认证的方法
if info.FullMethod == "/api.Auth/Login" || info.FullMethod == "/api.Auth/Register" {
return handler(ctx, req)
}

// 从元数据获取 token
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "缺少元数据")
}

values := md.Get("authorization")
if len(values) == 0 {
return nil, status.Error(codes.Unauthenticated, "缺少认证 token")
}

token := values[0]
if !strings.HasPrefix(token, "Bearer ") {
return nil, status.Error(codes.Unauthenticated, "无效的 token 格式")
}

// 验证 token
userID, err := validateToken(strings.TrimPrefix(token, "Bearer "))
if err != nil {
return nil, status.Error(codes.Unauthenticated, "无效的 token")
}

// 将用户信息添加到上下文
ctx = context.WithValue(ctx, "user_id", userID)

return handler(ctx, req)
}

// 验证 token(示例)
func validateToken(token string) (string, error) {
// 这里实现实际的 token 验证逻辑
// 可以使用 JWT 或查询数据库
return "user123", nil
}

func main() {
s := grpc.NewServer(
grpc.UnaryInterceptor(authInterceptor),
)
}

客户端发送 Token

func callWithToken(client pb.GreeterClient, token string) {
// 创建带 token 的元数据
md := metadata.New(map[string]string{
"authorization": "Bearer " + token,
})
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 调用服务
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
log.Printf("调用失败: %v", err)
return
}
log.Printf("响应: %s", resp.Message)
}

Per-RPC 认证

// 使用 PerRPCCredentials 接口
type tokenAuth struct {
token string
}

func (t *tokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"authorization": "Bearer " + t.token,
}, nil
}

func (t *tokenAuth) RequireTransportSecurity() bool {
return true // 要求 TLS
}

func main() {
// 创建认证凭证
auth := &tokenAuth{token: "my-jwt-token"}

// 连接时使用
conn, _ := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(auth),
)

// 所有调用都会自动带上 token
client := pb.NewGreeterClient(conn)
client.SayHello(ctx, req) // 自动带上 token
}

授权控制

基于角色的访问控制

// 角色定义
const (
RoleAdmin = "admin"
RoleUser = "user"
)

// 权限映射
var permissions = map[string][]string{
"/api.UserService/DeleteUser": {RoleAdmin},
"/api.UserService/ListUsers": {RoleAdmin, RoleUser},
"/api.UserService/GetUser": {RoleAdmin, RoleUser},
}

// 授权拦截器
func authzInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 获取用户角色
userID := ctx.Value("user_id").(string)
userRole := getUserRole(userID)

// 检查权限
allowedRoles, ok := permissions[info.FullMethod]
if !ok {
// 方法未定义权限,允许访问
return handler(ctx, req)
}

for _, role := range allowedRoles {
if role == userRole {
return handler(ctx, req)
}
}

return nil, status.Error(codes.PermissionDenied, "权限不足")
}

func getUserRole(userID string) string {
// 从数据库获取用户角色
return RoleUser
}

// 组合拦截器
func main() {
s := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
authInterceptor, // 认证
authzInterceptor, // 授权
)),
)
}

安全最佳实践

1. 生产环境必须使用 TLS

// ❌ 不好:不安全连接
grpc.WithTransportCredentials(insecure.NewCredentials())

// ✅ 好:使用 TLS
grpc.WithTransportCredentials(creds)

2. 证书管理

// 定期轮换证书
func loadCertificate(certPath, keyPath string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return tls.Certificate{}, err
}
return cert, nil
}

// 监控证书过期
func checkCertExpiry(cert *x509.Certificate) time.Duration {
return time.Until(cert.NotAfter)
}

3. 敏感信息处理

// ❌ 不好:日志中记录敏感信息
log.Printf("Token: %s", token)

// ✅ 好:隐藏敏感信息
log.Printf("User authenticated: %s", userID)

4. 限制请求大小

s := grpc.NewServer(
grpc.MaxRecvMsgSize(10*1024*1024), // 10MB
grpc.MaxSendMsgSize(10*1024*1024), // 10MB
)

5. 设置超时

// 服务端设置全局超时
func timeoutInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
return handler(ctx, req)
}

小结

本章我们学习了:

  1. TLS 加密:单向和双向 TLS 配置
  2. Token 认证:使用拦截器实现认证
  3. 授权控制:基于角色的访问控制
  4. 最佳实践:证书管理、敏感信息处理、请求限制

安全是生产环境 gRPC 服务的必备特性,务必正确配置。