前言

前面篇章的gRPC都是明文传输的,容易被篡改数据。本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证。

TLS证书认证

什么是TLS

TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。

TLS的作用

TLS协议主要解决如下三个网络安全问题。

  • 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
  • 完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
  • 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;

生成私钥

生成RSA私钥:openssl genrsa -out server.key 2048

生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512

生成ECC私钥:openssl ecparam -genkey -name secp384r1 -out server.key

生成ECC私钥,命令为椭圆曲线密钥参数生成及操作,本文中ECC曲线选择的是secp384r1

生成公钥

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl req:生成自签名证书,-new指生成证书请求、-sha256指使用sha256加密、-key指定私钥文件、-x509指输出证书、-days 3650为有效期

此后则输入证书拥有者信息

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
Organizational Unit Name (eg, section) []:Dev
Common Name (e.g. server FQDN or YOUR name) []:go-grpc-example
Email Address []:xxx@xxx.com

服务端构建TLS证书并认证

func main() {
// 监听本地端口
listener, err := net.Listen(Network, Address)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
// 从输入证书文件和密钥文件为服务端构造TLS凭证
creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
// 新建gRPC服务器实例,并开启TLS认证
grpcServer := grpc.NewServer(grpc.Creds(creds))
// 在gRPC服务器注册我们的服务
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
log.Println(Address + " net.Listing whth TLS and token...")
//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcServer.Serve err: %v", err)
}
}
  • credentials.NewServerTLSFromFile:从输入证书文件和密钥文件为服务端构造TLS凭证
  • grpc.Creds:返回一个ServerOption,用于设置服务器连接的凭证。

完整server.go代码

客户端配置TLS连接

var grpcClient pb.SimpleClient

func main() {
//从输入的证书文件中为客户端构造TLS凭证
creds, err := credentials.NewClientTLSFromFile("../pkg/tls/server.pem", "go-grpc-example")
if err != nil {
log.Fatalf("Failed to create TLS credentials %v", err)
}
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close() // 建立gRPC连接
grpcClient = pb.NewSimpleClient(conn)
}
  • credentials.NewClientTLSFromFile:从输入的证书文件中为客户端构造TLS凭证。
  • grpc.WithTransportCredentials:配置连接级别的安全凭证(例如,TLS/SSL),返回一个DialOption,用于连接服务器。

完整client.go代码

到这里,已经完成TLS证书认证了,gRPC传输不再是明文传输。此外,添加自定义的验证方法能使gRPC相对更安全。下面以Token认证为例,介绍gRPC如何添加自定义验证方法。

Token认证

客户端发请求时,添加Token到上下文context.Context中,服务器接收到请求,先从上下文中获取Token验证,验证通过才进行下一步处理。

客户端请求添加Token到上下文中

type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}

gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:

  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

// Token token认证
type Token struct {
AppID string
AppSecret string
} // GetRequestMetadata 获取当前请求认证所需的元数据(metadata)
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"app_id": t.AppID, "app_secret": t.AppSecret}, nil
} // RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {
return true
}

然后再客户端中调用Dial时添加自定义验证方法进去

//构建Token
token := auth.Token{
AppID: "grpc_token",
AppSecret: "123456",
}
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

完整client.go代码

服务端验证Token

首先需要从上下文中获取元数据,然后从元数据中解析Token进行验证

// Check 验证token
func Check(ctx context.Context) error {
//从上下文中获取元数据
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Errorf(codes.Unauthenticated, "获取Token失败")
}
var (
appID string
appSecret string
)
if value, ok := md["app_id"]; ok {
appID = value[0]
}
if value, ok := md["app_secret"]; ok {
appSecret = value[0]
}
if appID != "grpc_token" || appSecret != "123456" {
return status.Errorf(codes.Unauthenticated, "Token无效: app_id=%s, app_secret=%s", appID, appSecret)
}
return nil
} // Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
//检测Token是否有效
if err := Check(ctx); err != nil {
return nil, err
}
res := pb.SimpleResponse{
Code: 200,
Value: "hello " + req.Data,
}
return &res, nil
}
  • metadata.FromIncomingContext:从上下文中获取元数据

完整server.go代码

服务端代码中,每个服务的方法都需要添加Check(ctx)来验证Token,这样十分麻烦。gRPC拦截器,能很好地解决这个问题。gRPC拦截器功能类似中间件,拦截器收到请求后,先进行一些操作,然后才进入服务的代码处理。

服务端添加拦截器

func main() {
// 监听本地端口
listener, err := net.Listen(Network, Address)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
// 从输入证书文件和密钥文件为服务端构造TLS凭证
creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
//普通方法:一元拦截器(grpc.UnaryInterceptor)
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
//拦截普通方法请求,验证Token
err = Check(ctx)
if err != nil {
return
}
// 继续处理请求
return handler(ctx, req)
}
// 新建gRPC服务器实例,并开启TLS认证和Token认证
grpcServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))
// 在gRPC服务器注册我们的服务
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
log.Println(Address + " net.Listing whth TLS and token...")
//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcServer.Serve err: %v", err)
}
}
  • grpc.UnaryServerInterceptor:为一元拦截器,只会拦截简单RPC方法。流式RPC方法需要使用流式拦截器grpc.StreamInterceptor进行拦截。

客户端发起请求,当Token不正确时候,会返回

Call Route err: rpc error: code = Unauthenticated desc = Token无效: app_id=grpc_token, app_secret=12345

总结

本篇介绍如何为gRPC添加TLS证书认证和自定义认证,从而让gRPC更安全。添加gRPC拦截器,从而省略在每个方法前添加Token检测代码,使代码更简洁。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考:

Go gRPC进阶-TLS认证+自定义方法认证(七)的更多相关文章

  1. gRPC官方文档(安全认证)

    文章来自gRPC 官方文档中文版 认证 gRPC 被设计成可以利用插件的形式支持多种授权机制.本文档对多种支持的授权机制提供了一个概览,并且用例子来论述对应API,最后就其扩展性作了讨论. 马上将会推 ...

  2. C#进阶系列——WebApi 身份认证解决方案:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...

  3. 基于TLS的EAP 认证方法

    TLS: transport level security , 安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性.该协议由两层组成: TLS 记录协议(TLS Record)和 TLS ...

  4. php实现https(tls/ssl)双向认证

    php实现https(tls/ssl)双向认证 通常情况下,在部署https的时候,是基于ssl单向认证的,也就是说只要客户端认证服务器,而服务器不需要认证客户端. 但在一些安全性较高的场景,如银行, ...

  5. 基于mosquitto的MQTT服务器---SSL/TLS 单向认证+双向认证

    基于mosquitto的MQTT服务器---SSL/TLS 单向认证+双向认证 摘自:https://blog.csdn.net/ty1121466568/article/details/811184 ...

  6. #进阶系列——WebApi 身份认证解决方案:Basic基础认证

    阅读目录 一.为什么需要身份认证 二.Basic基础认证的原理解析 1.常见的认证方式 2.Basic基础认证原理 三.Basic基础认证的代码示例 1.登录过程 2./Home/Index主界面 3 ...

  7. [python](Docker SDK)上传镜像到私有仓库(tls、身份认证)

    (Docker SDK)上传镜像到私有仓库(tls.身份认证) API:https://docker-py.readthedocs.io/en/stable/ 环境:python:3.7.3 配置参数 ...

  8. Docker开启TLS和CA认证

    前言:Docker直接开启2375端口是不安全的,别人只要连上之后就可以任意操作,下面是开启Docker的TLS和CA认证方法,并使用Jenkins和Portainer连接. 一.生成证书 查看服务器 ...

  9. Go gRPC进阶-go-grpc-middleware使用(八)

    前言 上篇介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用.gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱.本篇简单介绍go-grpc-middleware的 ...

随机推荐

  1. TCP建立连接(三次握手)

    TCP的三次握手 TCP作为可靠稳定的传输方式要建立会话,会话的发起者是请求者(客户机),下文中A为客户机,B为服务器. 1.客户机(A)从closed状态转化为主动打开,服务器(B)此时从close ...

  2. Python科学计算库SymPy初探

    SymPy基础应用 .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { bord ...

  3. Session的常用方法

    Session的常用方法 session对象主要用于属性操作和会话管理,常用方法如下: 1.public void setAttribute(String name,String value)设定指定 ...

  4. Redis(十二):redis请求转发的实现

    请求转发一般的原因为: 1. 该请求自身无法处理,需要转发给对应的服务器处理: 2. 为实现负载均衡,使用路由服务,选择目标实例进行转发: 在集群模式下,请求可以打到任何一台redis服务器上.然而并 ...

  5. Jenkins的制品管理

    Jenkins的制品管理 制品是什么? 也叫产出物或工件.制品是软件开发过程中产生的多种有形副产品之一.广义的制品包括用例.UML图.设计文档等.而狭义的制品就可以简单地理解为二进制包.虽然有些代码是 ...

  6. C# 微信 生活助手 空气质量 天气预报等 效果展示 数据抓取 (二)

    此文主要是 中国天气网和中国环境监测总站的数据抓取 打算开放全部数据抓取源代码 已在服务器上 稳定运行半个月 webapi http://api.xuzhiheng.cn/ 常量 /// <su ...

  7. Java锁的深度化--重入锁、读写锁、乐观锁、悲观锁

    Java锁 锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized(重量级) 和 Reentr ...

  8. Mol Cell Proteomics. | MARMoSET – Extracting Publication-ready Mass Spectrometry Metadata from RAW Files

    本文是马克思普朗克心肺研究所的三名研究者Marina Kiweler.Mario Looso和Johannes Graumann发表在8月刊的MCP的一篇文章. 由于Omics实验经常涉及数百个数据文 ...

  9. Spring WebFlux 入门

    1. WebFlux介绍 Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式web框架.与Spring MVC不同,它不需要Servlet API,是完全异步 ...

  10. R之Shiny学习笔记

    官方教程:https://shiny.rstudio.com/tutorial/ 中文教程:http://yanping.me/shiny-tutorial/ 英文教程:https://deanatt ...