1.获取Grpc客户端的IP
golang客户端发给服务端的http请求,本质上就是一个Request的结构体(见net/http/request.go) 中除了包含header、body外还包含其他的附加信息,比如RemoteAddr(客户端的地址) 。这样http很容易就可以获取客户端的地址,详细解释如下:。
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string
那么使用grpc-go是否能够获取客户端的地址呢?
答案是肯定的,笔者查了下grpc的源码,发下peer包中包含了Addr(grpc/peer/peer.go),定义如下:
// Package peer defines various peer information associated with RPCs and
// corresponding utils.
package peer
import (
"net"
"golang.org/x/net/context"
"google.golang.org/grpc/credentials"
)
// Peer contains the information of the peer for an RPC, such as the address
// and authentication information.
type Peer struct {
// Addr is the peer address.
Addr net.Addr
// AuthInfo is the authentication information of the transport.
// It is nil if there is no transport security being used.
AuthInfo credentials.AuthInfo
}
type peerKey struct{}
// NewContext creates a new context with peer information attached.
func NewContext(ctx context.Context, p *Peer) context.Context {
return context.WithValue(ctx, peerKey{}, p)
}
// FromContext returns the peer information in ctx if it exists.
func FromContext(ctx context.Context) (p *Peer, ok bool) {
p, ok = ctx.Value(peerKey{}).(*Peer)
return
}
grpc的请求中默认都会有context的值,我们可以使用FromContext的方法获取Peer结构,最终取出客户端的ip地址。以下是笔者在自己的项目中使用获取的方式,仅供参考:
func getClietIP(ctx context.Context) (string, error) {
pr, ok := peer.FromContext(ctx)
if !ok {
return "", fmt.Errorf("[getClinetIP] invoke FromContext() failed")
}
if pr.Addr == net.Addr(nil) {
return "", fmt.Errorf("[getClientIP] peer.Addr is nil")
}
addSlice := strings.Split(pr.Addr.String(), ":")
return addSlice[0], nil
}
注意:在使用grpc stream的方式中,context的值可以直接从stream中获取,方法stream.Context()
2.Grpc双向验证
笔者在自己的项目中使用grpc的双向验证功能,此处使用的证书均为笔者自签获取。笔者的双向验证流程如下所示:
rootB.crt作为根证书签发testB.crt的二级证书
rootA.crt作为根证书签发testA.crt的二级证书
注意: testB.key和testA.key保存用户的私钥不会再网络上传输,笔者在理解双向验证时,是基于HTTPS的单向验证进行理解的。
HTTPS的单向验证理解:
- 浏览器会先预先植入一些可信网站的根证书,用户也可自行下载可信网站的根证书。
- 浏览器在使用https连接server端服务时,server端会首先将证书通过网络传输发送到浏览器(客户端)。
- 浏览器(客户端)在接收到server的证书后,验证server端的证书是否为自己签发的子证书。
client端双向验证代码
certificate, err := tls.LoadX509KeyPair(config.SelfCertFile, config.SelfKeyFile)
if err != nil {
log.Fatalf("load x509 key pair failed: %s", err)
}
certPool := x509.NewCertPool()
bs, err := ioutil.ReadFile(config.RootCAFile)
if err != nil {
log.Fatalf("fail to read ca cert: %s", err)
}
ok := certPool.AppendCertsFromPEM(bs)
if !ok {
log.Fatal("failed to append certs")
}
transportCreds := credentials.NewTLS(&tls.Config{ //核心产生一个传输通道的证书
ServerName: config.ServerName,
Certificates: []tls.Certificate{certificate},
RootCAs: certPool,
})
dialOption := grpc.WithTransportCredentials(transportCreds) //配置一个连接层的安全证书
grpcConn, err := grpc.Dial(grpcAddress, dialOption) //与grpc的server端建立连接
if err != nil {
log.WithError(err).Fatal("[NewReviewClient] create grpc conn failed")
}
...
- 主要使用的函数:
`func WithTransportCredentials(creds credentials.TransportCredentials) DialOption
- grpc还提供针对每个连接的访问授权
WithPerRPCCredentials()
Server端的双向验证代码
caCert, err := ioutil.ReadFile(remoteCAFile)
if err != nil {
log.WithError(err).Fatal("Fail to load client root ca certs")
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
log.Fatal("Failed to append client certs")
}
certificate, err := tls.LoadX509KeyPair(localCrtFile, localKeyFile)
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{certificate},
ClientCAs: caCertPool,
}
svrOption := grpc.Creds(credentials.NewTLS(tlsConfig))
//Creds函数返回一个已经包含证书的server端连接ServerOption结构
...
主要使用的函数:
func Creds(c credentials.TransportCredentials) ServerOption