Go gRPC进阶-TLS认证+自定义方法认证(七)
<h3 id="前言">前言</h3><p>前面篇章的gRPC都是明文传输的,容易被篡改数据。本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证。</p>
<h3 id="tls证书认证">TLS证书认证</h3>
<h6 id="什么是tls">什么是TLS</h6>
<p>TLS(Transport Layer Security,安全传输层),TLS是建立在<code>传输层</code>TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。</p>
<h6 id="tls的作用">TLS的作用</h6>
<p>TLS协议主要解决如下三个网络安全问题。</p>
<ul>
<li>保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;</li>
<li>完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;</li>
<li>认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;</li>
</ul>
<h4 id="生成私钥">生成私钥</h4>
<p>生成RSA私钥:<code>openssl genrsa -out server.key 2048</code></p>
<blockquote>
<p>生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512</p>
</blockquote>
<p>生成ECC私钥:<code>openssl ecparam -genkey -name secp384r1 -out server.key</code></p>
<blockquote>
<p>生成ECC私钥,命令为椭圆曲线密钥参数生成及操作,本文中ECC曲线选择的是secp384r1</p>
</blockquote>
<h4 id="生成公钥">生成公钥</h4>
<p><code>openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650</code></p>
<blockquote>
<p>openssl req:生成自签名证书,-new指生成证书请求、-sha256指使用sha256加密、-key指定私钥文件、-x509指输出证书、-days 3650为有效期</p>
</blockquote>
<p>此后则输入证书拥有者信息</p>
<pre><code>Country Name (2 letter code) :CN
State or Province Name (full name) :XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) :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
</code></pre>
<h4 id="服务端构建tls证书并认证">服务端构建TLS证书并认证</h4>
<pre><code class="language-go">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)
}
}
</code></pre>
<ul>
<li><code>credentials.NewServerTLSFromFile</code>:从输入证书文件和密钥文件为服务端构造TLS凭证</li>
<li><code>grpc.Creds</code>:返回一个ServerOption,用于设置服务器连接的凭证。</li>
</ul>
<p>完整server.go代码</p>
<h4 id="客户端配置tls连接">客户端配置TLS连接</h4>
<pre><code class="language-go">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)
}
</code></pre>
<ul>
<li><code>credentials.NewClientTLSFromFile</code>:从输入的证书文件中为客户端构造TLS凭证。</li>
<li><code>grpc.WithTransportCredentials</code>:配置连接级别的安全凭证(例如,TLS/SSL),返回一个DialOption,用于连接服务器。</li>
</ul>
<p>完整client.go代码</p>
<p>到这里,已经完成TLS证书认证了,gRPC传输不再是明文传输。此外,添加自定义的验证方法能使gRPC相对更安全。下面以Token认证为例,介绍gRPC如何添加自定义验证方法。</p>
<h3 id="token认证">Token认证</h3>
<p>客户端发请求时,添加Token到上下文<code>context.Context</code>中,服务器接收到请求,先从上下文中获取Token验证,验证通过才进行下一步处理。</p>
<h4 id="客户端请求添加token到上下文中">客户端请求添加Token到上下文中</h4>
<pre><code class="language-go">type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (mapstring, error)
RequireTransportSecurity() bool
}
</code></pre>
<p>gRPC 中默认定义了 <code>PerRPCCredentials</code>,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:</p>
<ul>
<li><code>GetRequestMetadata</code>:获取当前请求认证所需的元数据</li>
<li><code>RequireTransportSecurity</code>:是否需要基于 TLS 认证进行安全传输</li>
</ul>
<p>接下来我们实现这两个方法</p>
<pre><code class="language-go">// Token token认证
type Token struct {
AppID string
AppSecret string
}
// GetRequestMetadata 获取当前请求认证所需的元数据(metadata)
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (mapstring, error) {
return mapstring{"app_id": t.AppID, "app_secret": t.AppSecret}, nil
}
// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {
return true
}
</code></pre>
<p>然后再客户端中调用Dial时添加自定义验证方法进去</p>
<pre><code class="language-go">//构建Token
token := auth.Token{
AppID: "grpc_token",
AppSecret: "123456",
}
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
</code></pre>
<p>完整client.go代码</p>
<h4 id="服务端验证token">服务端验证Token</h4>
<p>首先需要从上下文中获取元数据,然后从元数据中解析Token进行验证</p>
<pre><code class="language-go">// 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
}
if value, ok := md["app_secret"]; ok {
appSecret = value
}
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
}
</code></pre>
<ul>
<li><code>metadata.FromIncomingContext</code>:从上下文中获取元数据</li>
</ul>
<p>完整server.go代码</p>
<p>服务端代码中,每个服务的方法都需要添加Check(ctx)来验证Token,这样十分麻烦。gRPC拦截器,能很好地解决这个问题。gRPC拦截器功能类似中间件,拦截器收到请求后,先进行一些操作,然后才进入服务的代码处理。</p>
<h3 id="服务端添加拦截器">服务端添加拦截器</h3>
<pre><code class="language-go">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)
}
}
</code></pre>
<ul>
<li><code>grpc.UnaryServerInterceptor</code>:为一元拦截器,只会拦截简单RPC方法。流式RPC方法需要使用流式拦截器<code>grpc.StreamInterceptor</code>进行拦截。</li>
</ul>
<p>客户端发起请求,当Token不正确时候,会返回</p>
<pre><code class="language-powershell">Call Route err: rpc error: code = Unauthenticated desc = Token无效: app_id=grpc_token, app_secret=12345
</code></pre>
<h3 id="总结">总结</h3>
<p>本篇介绍如何为gRPC添加TLS证书认证和自定义认证,从而让gRPC更安全。添加gRPC拦截器,从而省略在每个方法前添加Token检测代码,使代码更简洁。</p>
<p>教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example</p>
<p>参考:</p>
<blockquote>
<ul>
<li>https://www.jianshu.com/p/1fc7130eb2c2</li>
<li>https://segmentfault.com/a/1190000007933303</li>
<li>https://eddycjy.com/posts/go/grpc/2018-10-07-grpc-tls/</li>
<li>https://segmentfault.com/a/1190000007997759</li>
<li>https://eddycjy.com/posts/go/grpc/2018-10-10-interceptor/</li>
</ul>
</blockquote>
</div>
<div id="MySignature" role="contentinfo">
看完之后若觉得对自己有帮助,恳请点赞或评论。这是对我最大的鼓励!<br><br>
来源:https://www.cnblogs.com/FireworksEasyCool/p/12710325.html
頁:
[1]