安全认证
本章介绍 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)
}
小结
本章我们学习了:
- TLS 加密:单向和双向 TLS 配置
- Token 认证:使用拦截器实现认证
- 授权控制:基于角色的访问控制
- 最佳实践:证书管理、敏感信息处理、请求限制
安全是生产环境 gRPC 服务的必备特性,务必正确配置。