gRPC教程 — 使用TLS时相关问体积解决办法

  • 本文代码
  • 问题重现
  • 解决办法
  • 一、生成CA根证书
    • 1.1 在证书存放文件夹下 新建 `ca.conf`,写入内容如下:
    • 1.2 生成ca秘钥,得到ca.key
    • 1.3 生成ca证书签发请求,得到ca.csr
    • 1.4 生成ca根证书,得到ca.crt
  • 二、 生成服务端证书
    • 2.1 在证书存放文件夹下,新建`server.conf`,写入内容如下:
    • 2.2 生成秘钥,得到server.key
    • 2.3 生成证书签发请求,得到server.csr
    • 2.4 用CA证书生成服务端证书,得到server.pem
  • ca.conf和server.conf对比
  • 三、证书的使用(单向认证)
    • 3.1 服务端
    • 3.2 客户端
  • 四、双向认证
  • 注意事项
    • 4.1 修改根证书生成命令
      • 4.1.1 生成ca秘钥,得到ca.key【命令与1.2完全一致】
      • 4.1.2 生成ca证书签发请求,得到ca.csr【命令与1.3完全一致】
      • 4.1.3 生成ca根证书,得到`ca.pem`的命令:
    • 4.2 修改服务端证书生成命令
      • 4.2.1 生成秘钥,得到server.key【命令与2.2完全一致】
      • 4.2.2 生成证书签发请求,得到server.csr【命令与2.3完全一致】
      • 4.2.3 用CA证书生成服务端证书,得到server.pem
    • 4.3 在证书存放文件夹下,新建client.conf,写入内容如下:
    • 4.4 生成秘钥,得到client.key
    • 4.5 生成证书签发请求,得到client.csr
    • 4.6 用CA证书生成客户端证书,得到client.pem
    • 4.7 服务端代码
    • 4.8 客户端代码
  • 五、Token认证
    • 5.1 服务端代码
    • 5.2 客户端
      • 5.2.1 客户端需要实现 `PerRPCCredentials` 接口
    • 5.3 客户端
  • 六、拦截器

本文代码

码云:https://gitee.com/XiMuQi/go-grpc

问题重现

golang 1.15+版本上,用 gRPC通过TLS实现数据传输加密时,会报错证书的问题:

rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate is not valid for any names, but wanted to match www.ximu.info"

是因为用的TLS证书,并没有开启SAN扩展(默认是没有开启SAN扩展)所生成的,导致客户端和服务端无法建立连接。

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

解决办法

一、生成CA根证书

后面需要使用根证书CA来生成服务端、客户端证书

1.1 在证书存放文件夹下 新建 ca.conf,写入内容如下:

#req 总的配置
[ req ]
default_bits       = 4096
distinguished_name = req_distinguished_name #使用 req_distinguished_name配置模块[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = CN
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = BeiJing
localityName                = Locality Name (eg, city)
localityName_default        = BeiJing
organizationName            = Organization Name (eg, company)
organizationName_default    = XiMu
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_max              = 64
commonName_default          = ximu

1.2 生成ca秘钥,得到ca.key

openssl genrsa -out ca.key 4096

1.3 生成ca证书签发请求,得到ca.csr

openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf

1.4 生成ca根证书,得到ca.crt

openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt

二、 生成服务端证书

2.1 在证书存放文件夹下,新建server.conf,写入内容如下:

#req 总配置
[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name  #使用 req_distinguished_name配置模块
req_extensions     = req_ext  #使用 req_ext配置模块[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = CN
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = BeiJing
localityName                = Locality Name (eg, city)
localityName_default        = BeiJing
organizationName            = Organization Name (eg, company)
organizationName_default    = XiMu
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_max              = 64
commonName_default          = ximu    #这里的Common Name 写主要域名即可(注意:这个域名也要在alt_names的DNS.x里) 此处尤为重要,需要用该服务名字填写到客户端的代码中[ req_ext ]
subjectAltName = @alt_names #使用 alt_names配置模块[alt_names]
DNS.1   = localhost
DNS.2   = ximu.info
DNS.3   = www.ximu.info
IP      = 127.0.0.1

2.2 生成秘钥,得到server.key

openssl genrsa -out server.key 2048

2.3 生成证书签发请求,得到server.csr

openssl req -new -sha256 -out server.csr -key server.key -config server.conf

2.4 用CA证书生成服务端证书,得到server.pem

openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extensions req_ext -extfile server.conf

现在证书已经生成完毕, server.pem 和 server.key就是我们需要的证书和密钥。

key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
pem: 是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER。

ca.conf和server.conf对比

可以发现ca.conf和server.conf的配置大致相同,这是因为ca.conf生成的是根证书,用户证书需要在根证书的基础上创建,所以高度相同,对于两个证书的配置信息不必过于深究,够使用即可。


三、证书的使用(单向认证)

3.1 服务端

package mainimport ("context""google.golang.org/grpc""google.golang.org/grpc/credentials""google.golang.org/grpc/grpclog""grpc01/service""log""net"
)//1、声明一个server,里面是未实现的字段
type server struct {service.UnimplementedQueryUserServer
}//2、必须要实现在hello.proto里声明的远程调用接口,否则客户端会报:
//rpc error: code = Unimplemented desc = method GetUserInfo not implemente
func (s *server) GetUserInfo(ctx context.Context, in *service.ReqParam) (*service.ResParam, error) {return &service.ResParam{Id: in.Id, Name: in.Name, Age: 20, Address: "Beijing"}, nil
}func main() {//配置 TLS认证相关文件creds, err := credentials.NewServerTLSFromFile("keys/server.pem", "keys/server.key")if err != nil {grpclog.Fatalf("Failed to generate credentials %v", err)}//1、创建服务,并开启TLS认证//ser := grpc.NewServer()ser := grpc.NewServer(grpc.Creds(creds))//2、注册服务service.RegisterQueryUserServer(ser, &server{})//3、监听服务端口listener, err := net.Listen("tcp", ":8002")if err != nil {log.Fatal("服务监听端口失败", err)}//4、启动服务_ = ser.Serve(listener)
}

3.2 客户端

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials""google.golang.org/grpc/grpclog""grpc01/service""log"
)func main() {// TLS连接  注意serverNameOverride填写的是在server.conf的[alt_names]里的DNS.X地址creds, err := credentials.NewClientTLSFromFile("keys/server.pem", "localhost")if err != nil {grpclog.Fatalf("Failed to create TLS credentials %v", err)}//1、 建立连接//conn, err := grpc.Dial(":8002", grpc.WithInsecure())conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(creds))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()request := &service.ReqParam{Id:   123,Name: "西木",}// 2. 调用 hello_grpc.pb.go 中的NewQueryUserClient方法建立客户端query := service.NewQueryUserClient(conn)//3、调用rpc方法res, err := query.GetUserInfo(context.Background(), request)if err != nil {log.Fatal("调用gRPC方法错误: ", err)}fmt.Println("调用gRPC方法成功,ProdStock = ", res)}

四、双向认证

注意事项

前面我们生成的根证书是ca.crt,在双向认证时,我使用的是ca.pem,所以需要更改一下证书的类型。
只需将1.4的生成ca.crt的命令改为ca.pem即可;

4.1 修改根证书生成命令

4.1.1 生成ca秘钥,得到ca.key【命令与1.2完全一致】

openssl genrsa -out ca.key 4096

4.1.2 生成ca证书签发请求,得到ca.csr【命令与1.3完全一致】

openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf

4.1.3 生成ca根证书,得到ca.pem的命令:

openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.pem

4.2 修改服务端证书生成命令

4.2.1 生成秘钥,得到server.key【命令与2.2完全一致】

openssl genrsa -out server.key 2048

4.2.2 生成证书签发请求,得到server.csr【命令与2.3完全一致】

openssl req -new -sha256 -out server.csr -key server.key -config server.conf

4.2.3 用CA证书生成服务端证书,得到server.pem

ca.crt替换成ca.pem 使用ca.pem的命令:

openssl x509 -req -days 3650 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extensions req_ext -extfile server.conf

4.3 在证书存放文件夹下,新建client.conf,写入内容如下:

#req 总配置
[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name  #使用 req_distinguished_name配置模块
req_extensions     = req_ext  #使用 req_ext配置模块[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = CN
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = BeiJing
localityName                = Locality Name (eg, city)
localityName_default        = BeiJing
organizationName            = Organization Name (eg, company)
organizationName_default    = XiMu
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_max              = 64
commonName_default          = ximu    #这里的Common Name 写主要域名即可(注意:这个域名也要在alt_names的DNS.x里) 此处尤为重要,需要用该服务名字填写到客户端的代码中[ req_ext ]
subjectAltName = @alt_names #使用 alt_names配置模块[alt_names]
DNS.1   = localhost
DNS.2   = ximu.info
DNS.3   = www.ximu.info
IP      = 127.0.0.1

4.4 生成秘钥,得到client.key

openssl ecparam -genkey -name secp384r1 -out client.key

4.5 生成证书签发请求,得到client.csr

openssl req -new -sha256 -out client.csr -key client.key -config client.conf

4.6 用CA证书生成客户端证书,得到client.pem

使用ca.pem的命令:

openssl x509 -req -days 3650 -CA ca.pem -CAkey ca.key -CAcreateserial -in client.csr -out client.pem -extensions req_ext -extfile client.conf

4.7 服务端代码

ca.pemserver.keyserver.pem到服务端代码中:

package mainimport ("context""crypto/tls""crypto/x509""go-grpc/service""google.golang.org/grpc""google.golang.org/grpc/credentials""io/ioutil""log""net"
)//1、声明一个server,里面是未实现的字段
type server struct {service.UnimplementedQueryUserServer
}//2、必须要实现在hello.proto里声明的远程调用接口,否则客户端会报:
//rpc error: code = Unimplemented desc = method GetUserInfo not implemente
func (s *server) GetUserInfo(ctx context.Context, in *service.ReqParam) (*service.ResParam, error) {return &service.ResParam{Id: in.Id, Name: in.Name, Age: 20, Address: "Beijing"}, nil
}const (// Address gRPC服务地址Address = "127.0.0.1:8003"
)func main() {// TLS认证//从证书相关文件中读取和解析信息,得到证书公钥、密钥对cert, _ := tls.LoadX509KeyPair("keys2/server.pem", "keys2/server.key")//初始化一个CertPoolcertPool := x509.NewCertPool()ca, _ := ioutil.ReadFile("keys2/ca.pem")//注意这里只能解析pem类型的根证书,所以需要的是ca.pem//解析传入的证书,解析成功会将其加到池子中certPool.AppendCertsFromPEM(ca)//构建基于TLS的TransportCredentials选项creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert},        //服务端证书链,可以有多个ClientAuth:   tls.RequireAndVerifyClientCert, //要求必须验证客户端证书ClientCAs:    certPool,                       //设置根证书的集合,校验方式使用 ClientAuth 中设定的模式})//1、创建服务,并开启TLS认证//ser := grpc.NewServer()//ser := grpc.NewServer(grpc.Creds(creds), grpc.KeepaliveParams(keepalive.ServerParameters{//   MaxConnectionIdle: 5 * time.Minute, //这个连接最大的空闲时间,超过就释放,解决proxy等到网络问题(不通知grpc的client和server)//}))ser := grpc.NewServer(grpc.Creds(creds))//2、注册服务service.RegisterQueryUserServer(ser, &server{})//3、监听服务端口listener, err := net.Listen("tcp", Address)if err != nil {log.Fatal("服务监听端口失败", err)}//4、启动服务_ = ser.Serve(listener)
}

4.8 客户端代码

ca.pemclient.keyclient.pem到客户端代码中:

package mainimport ("context""crypto/tls""crypto/x509""fmt""go-grpc/service""google.golang.org/grpc""google.golang.org/grpc/credentials""io/ioutil""log"
)const (// Address gRPC服务地址Address = "127.0.0.1:8003"
)func main() {// 证书认证-双向认证// 从证书相关文件中读取和解析信息,得到证书公钥、密钥对cert, _ := tls.LoadX509KeyPair("keys2/client.pem", "keys2/client.key")// 创建一个新的、空的 CertPoolcertPool := x509.NewCertPool()ca, _ := ioutil.ReadFile("keys2/ca.pem")//注意这里只能解析pem类型的根证书,所以需要的是ca.pem// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用certPool.AppendCertsFromPEM(ca)// 构建基于 TLS 的 TransportCredentials 选项creds := credentials.NewTLS(&tls.Config{// 设置证书链,允许包含一个或多个Certificates: []tls.Certificate{cert},ServerName: "ximu.info", //注意这里的参数为配置文件中所允许的ServerName,也就是其中配置的DNS...RootCAs:    certPool,})//1、 建立连接conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()request := &service.ReqParam{Id:   123,Name: "西木",}// 2. 调用 hello_grpc.pb.go 中的NewQueryUserClient方法建立客户端query := service.NewQueryUserClient(conn)//3、调用rpc方法res, err := query.GetUserInfo(context.Background(), request)if err != nil {log.Fatal("调用gRPC方法错误: ", err)}fmt.Println("双向认证:调用gRPC方法成功,ProdStock = ", res)}

五、Token认证

5.1 服务端代码

package mainimport ("context""fmt""go-grpc/service""google.golang.org/grpc""google.golang.org/grpc/codes""google.golang.org/grpc/metadata""google.golang.org/grpc/status""log""net"
)//1、声明一个server,里面是未实现的字段
type server struct {service.UnimplementedQueryUserServer
}//2、必须要实现在hello.proto里声明的远程调用接口,否则客户端会报:
//rpc error: code = Unimplemented desc = method GetUserInfo not implemente
func (s *server) GetUserInfo(ctx context.Context, in *service.ReqParam) (*service.ResParam, error) {return &service.ResParam{Id: in.Id, Name: in.Name, Age: 20, Address: "Beijing"}, nil
}const (// Address gRPC服务地址Address = "127.0.0.1:8003"
)func main() {var authInterceptor grpc.UnaryServerInterceptor//匿名方法authInterceptor = func(ctx context.Context,req interface{},info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) (resp interface{}, err error) {//拦截普通方法请求,验证 Tokenerr = Auth(ctx)if err != nil {return}// 继续处理请求return handler(ctx, req)}ser := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))//2、注册服务service.RegisterQueryUserServer(ser, &server{})//3、监听服务端口listener, err := net.Listen("tcp", Address)if err != nil {log.Fatal("服务监听端口失败", err)}//4、启动服务_ = ser.Serve(listener)
}func Auth(ctx context.Context) error {md, ok := metadata.FromIncomingContext(ctx)if !ok {return fmt.Errorf("missing credentials")}var user stringvar password stringif val, ok := md["user"]; ok {user = val[0]}if val, ok := md["password"]; ok {password = val[0]}if user != "admin" || password != "admin" {return status.Errorf(codes.Unauthenticated, "客户端请求的token不合法")}return nil
}

5.2 客户端

5.2.1 客户端需要实现 PerRPCCredentials 接口

package serviceimport "context"type PerRPCCredentials interface {//GetRequestMetadata 方法返回认证需要的必要信息GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)//RequireTransportSecurity 方法表示是否启用安全链接,在生产环境中,一般都是启用的,但为了测试方便,暂时这里不启用RequireTransportSecurity() bool
}

具体实现:

package implimport "context"type Authentication struct {User     stringPassword string
}func (a *Authentication) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {return map[string]string{"user": a.User, "password": a.Password}, nil
}func (a *Authentication) RequireTransportSecurity() bool {return false
}

5.3 客户端

package mainimport ("context""fmt""go-grpc/service""go-grpc/token/service/impl""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""log"
)const (// Address gRPC服务地址Address = "127.0.0.1:8003"
)func main() {user := &impl.Authentication{User:     "admin",Password: "admin2",}//1、 建立连接conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(user))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()request := &service.ReqParam{Id:   123,Name: "西木",}// 2. 调用 hello_grpc.pb.go 中的NewQueryUserClient方法建立客户端query := service.NewQueryUserClient(conn)//3、调用rpc方法res, err := query.GetUserInfo(context.Background(), request)if err != nil {log.Fatal("调用gRPC方法错误: ", err)}fmt.Println("Token:调用gRPC方法成功,ProdStock = ", res)}

六、拦截器

参考:拦截器

gRPC教程 — TLS单向认证、双向认证、Token认证、拦截器相关推荐

  1. shiro将session认证改成token认证_初步学习Shiro框架 第一集

    1.什么是Shiro Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authent ...

  2. shiro将session认证改成token认证_Shiro 运行过程

    什么是shiro shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. shiro架构 subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体 ...

  3. WCF简单教程(6) 单向与双向通讯

    第六篇:单向与双向通讯 项目开发中我们时常会遇到需要异步调用的问题,有时忽略服务端的返回值,有时希望服务端在需要的时候回调,今天就来看看在WCF中如何实现. 先看不需要服务端返回值的单向调用,老规矩, ...

  4. springboot + vue 后台token生成 拦截器 redis实现 前台封装axios xueX 接口实现

    后台 后台程序图片 新建token的基础类 public class Constants {public final static String TOKEN = "token";} ...

  5. gRPC教程 — grpc-gateway

    gRPC教程 - grpc-gateway 前言 代码 一.Grpc网关介绍 1.1 原因 1.2 补充 1.3 流程 1.4 流程图 二.环境配置 2.1 需要的依赖 2.1.1 proto转go ...

  6. 微服务常见安全认证方案Session token cookie跨域

    HTTP 基本认证 HTTP Basic Authentication(HTTP 基本认证)是 HTTP 1.0 提出的一种认证机制,这个想必大家都很熟悉了,我不再赘述.HTTP 基本认证的过程如下: ...

  7. 路由守卫 AJAX,vue路由导航守卫 和 请求拦截以及基于node的token认证

    #####什么时候需要登录验证与权限控制 1.业务系统通常需要登录才能访问受限资源,在用户未登录情况下访问受限资源需要重定向到登录页面: 2.多个业务系统之间要实现单点登录,即在一个系统或应用已登录的 ...

  8. 常用的认证机制之session认证和token认证

    一.session认证 1.session认证的过程: 前端输入用户名和密码进行登录操作,后端拿到用户名和密码后,会把md5进行加密,加密之后,拿上加密后的密文到用户表中查找密文是否一致,判断用户是否 ...

  9. 认证学习4 - Bearer认证(Token认证)讲解、代码实现、演示

    文章目录 Bearer认证(JWT-Token)- 用户信息存客户端中 讲解(Bearer认证) 实现(Bearer认证) 代码(Bearer认证) 演示(Bearer认证) 浏览器 postman ...

最新文章

  1. Android webView 支持缩放及自适应屏幕
  2. Python PIL ImageDraw 和ImageFont模块学习
  3. 国内“重量级”单体数据中心开始运营 火了这个县
  4. 技术人的生命之源在于绝不固步自封而不断进取的精神
  5. vscode 新建cpp文件_利用vscode搭建c
  6. 【计算机网络】关键词汇翻译整合
  7. matlab中-psi_建议收藏 | 生物信息学中的可变剪切,这些内容你了解吗?
  8. checkbox 多选 mysql 搜索_mySQL技术的方方面面,不管是应用还是面试,看这一文就够了...
  9. 书评 – 程序员经典读物(2)
  10. oracle 上搭建ogg文档,ogg搭建配置实现oracle数据同步到mysql)
  11. 【周博磊】强化学习纲要 一至六讲笔记
  12. DRM:Digital Rights Management数字版权加密保护技术
  13. 「学点C语言系列」02 判断年份是否为闰年
  14. 6 数据库设计:实体-联系方法
  15. BottomSheetDialog禁止下滑关闭
  16. 【centos】geoserver支持ecw
  17. ORM一键还原系统官方版
  18. 大印文化:学习说服力一定要理解换框!
  19. iOS 设置字体 自定义字体
  20. 用Python写一个图片标注工具

热门文章

  1. [蓝桥杯][算法提高VIP]去注释
  2. 计算机网络基础知识及应用教学视频,计算机网络基础教程介绍以及视频讲解
  3. Windows 7 开机自动拨号 常用的五种方法
  4. 关于餐饮软件开源相关(2)
  5. 迅雷7.9版本缺少wlanapi等dll解决方法
  6. LCD字体前端实现(最详细)
  7. Express Invoice Plus for Mac是什么软件?Mac发票管理软件推荐!
  8. 网上书店 China-pub和第二书店
  9. 机器学习--高等数学篇--线性代数篇04--矩阵03(完)
  10. array方法之from方法