grpc加TLS加密和令牌认证

(金庆的专栏 2018.11)

用 golang 创建 grpc 服务,开启 TLS 加密,并采用令牌认证。
然后用 C++ 和 golang 分别创建客户端连接服务器。

参考:
https://segmentfault.com/a/1190000007933303

服务器

import (...grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth""google.golang.org/grpc""google.golang.org/grpc/credentials"
)func main() {listen, err := net.Listen("tcp", ":12345")if err != nil {grpclog.Fatalf("failed to listen: %v", err)}// TLS认证creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key")if err != nil {grpclog.Fatalf("Failed to generate credentials %v", err)}// 实例化grpc Server, 并开启TLS认证s := grpc.NewServer(grpc.Creds(creds),grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auth.Authenticate)),grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auth.Authenticate)))// 注册HelloServicepb.RegisterHelloServer(s, HelloService)grpclog.Println("Listen on " + Address + " with TLS")s.Serve(listen)
}

其中 server.key 是私钥,server.crt 是自签名证书,如下生成:

$ openssl genrsa -out server.key 2048
$ openssl req -new -x509 -sha256 -key server.key \-out server.crt -days 36500 \-subj /C=CN/ST=Shanghai/L=Songjiang/O=ztgame/OU=tech/CN=mydomain.ztgame.com/emailAddress=myname@ztgame.com

查看证书文件

$ openssl x509 -in server.crt -noout -text

auth.Authenticate 如下,作为 interceptor, 对每个请求进行令牌验证。

package authimport ("context""sync""google.golang.org/grpc/codes""google.golang.org/grpc/metadata""google.golang.org/grpc/status"
)// from token.yaml file
var tokenToAppName = &sync.Map{}func init() {tokenToAppName.Store("test", "test")
}// XXX load tokenToAppName from file// Authenticate checks that a token exists and is valid.
// It removes the token from the context and
//  stores the app name of the token in the returned context
func Authenticate(ctx context.Context) (context.Context, error) {token, err := extractHeader(ctx, "authorization-token")if err != nil {return ctx, err}// Remove token from headers from here onctx = purgeHeader(ctx, "authorization-token")valAppName, ok := tokenToAppName.Load(token)if !ok {return ctx, status.Errorf(codes.Unauthenticated, "no app for token '%s'", token)}appName := valAppName.(string)return context.WithValue(ctx, keyAppName{}, appName), nil
}func extractHeader(ctx context.Context, header string) (string, error) {md, ok := metadata.FromIncomingContext(ctx)if !ok {return "", status.Error(codes.Unauthenticated, "no headers in request")}authHeaders, ok := md[header]if !ok {return "", status.Error(codes.Unauthenticated, "no header in request")}if len(authHeaders) != 1 {return "", status.Error(codes.Unauthenticated, "more than 1 header in request")}return authHeaders[0], nil
}func purgeHeader(ctx context.Context, header string) context.Context {md, _ := metadata.FromIncomingContext(ctx)mdCopy := md.Copy()mdCopy[header] = nilreturn metadata.NewIncomingContext(ctx, mdCopy)
}type keyAppName struct{}// GetAppName can be used to extract app name stored in a context.
func GetAppName(ctx context.Context) string {// Authenticate()之后必然存在app namereturn ctx.Value(keyAppName{}).(string)
}

tokenToAppName 是一个map, 将合法的令牌映射为应用名。
每个应用(即用户)分配一个令牌,根据令牌可查到该用户是否合法,以及用户的其他信息。
这里只需要应用名。

每个请求将调用 Authenticate(), 该方法将从 http 头获取请求的令牌,查找对应的应用名,
ctx 中将删除令牌,替换成应用名。

GetAppName()将从 ctx 中获取应用名。

服务方法实现如下:

func (s MailServer) Get(ctx context.Context, r *pb.GetRequest) (*pb.GetResponse, error) {app := auth.GetAppName(ctx)body, err := db.NewGetter(app).GetMailBody(r.MailIndex)return &pb.GetResponse{Result: getResult(err),Body:   body,}, nil
}

先获取应用名,然后根据应用名获取相应的数据返回。

客户端

golang

 // Create the client TLS credentialscreds, err := credentials.NewClientTLSFromFile("key/server.crt", "mydomain.ztgame.com")if err != nil {panic(fmt.Errorf("could not load tls cert: %s", err))}// We don't need to error here, as this creates a pool and connections// will happen laterconn, _ := grpc.Dial(serviceURL,grpc.WithTransportCredentials(creds),grpc.WithPerRPCCredentials(auth.TokenAuth{Token: "test",}))cli := pb.NewMailClient(conn)

客户端只需要 server.crt, 其中包含服务器的公钥。
NewClientTLSFromFile() 的第2个参数是个域名,是 server.crt 中的域名。
目前测试阶段还没有正式域名设置,所以输入一个指定域名用于验证 server.crt 中的域名。
生产环境运行时,应该不需要这个域名,可以直接查询 DNS 进行验证。

Dial() 输入一个 WithPerRPCCredentials 用于令牌验证。

auth.TokenAuth 需要实现 PerRPCCredentials 接口:

package authimport ("context"
)type TokenAuth struct {Token string
}func (t TokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {return map[string]string{"authorization-token": t.Token,}, nil
}func (TokenAuth) RequireTransportSecurity() bool {return true
}

“authorization-token” 是客户端和服务器约定好的http认证头字符串。

C++

C++ 端的客户端代码比golang的稍复杂,因为 grpc C++ 库没有 grpc-go 成熟。

代码参照 grpc 示例 greeter_async_client2.cc:

int main(int argc, char** argv) {grpc::SslCredentialsOptions ssl_options;ssl_options.pem_root_certs = SERVER_CRT;// Create a default SSL ChannelCredentials object.auto channel_creds = grpc::SslCredentials(ssl_options);grpc::ChannelArguments cargs;cargs.SetSslTargetNameOverride("mydomain.ztgame.com");  // 如果加了 DNS 就不用这个了auto call_creds = grpc::MetadataCredentialsFromPlugin(std::unique_ptr<grpc::MetadataCredentialsPlugin>(new TokenAuthenticator(TOKEN)));auto compsited_creds = grpc::CompositeChannelCredentials(channel_creds, call_creds);// Create a channel using the credentials created in the previous step.auto channel = grpc::CreateCustomChannel("1.2.3.4:8000", compsited_creds, cargs);// Instantiate the client.MailClient tester(channel);...return 0;
}

因为 C++ 没有提供从文件读取 server.crt 的接口,所以在此直接用了一个常量字符串:

    ssl_options.pem_root_certs = SERVER_CRT;

SERVER_CRT 定义如下:

// server.crt 的内容
const char SERVER_CRT[] = R"(
-----BEGIN CERTIFICATE-----
TjERMA8GA1UECAwIU2hhbmdoYWkxEjAQBgNVBAcMCVNvbmdqaWFuZzEPMA0GA1UE
...
E6v50RCQgtWGmna+oy1I2UTVABdjBFnyKPEuz106mBfOhT6cg80hBHVgrV7sLHq8
76QolJm8yzZPL1qpiO4dKHHsCP6R
-----END CERTIFICATE-----
)";

TokenAuthenticator 定义如下,是个自定义认证插件:

// TokenAuthenticator 用来支持令牌认证
// https://grpc.io/docs/guides/auth.html
class TokenAuthenticator : public grpc::MetadataCredentialsPlugin {
public:TokenAuthenticator(const std::string& token) : token_(token) {}grpc::Status GetMetadata(grpc::string_ref service_url, grpc::string_ref method_name,const grpc::AuthContext& channel_auth_context,std::multimap<grpc::string, grpc::string>* metadata) override {metadata->insert(std::make_pair("authorization-token", token_));return grpc::Status::OK;}private:std::string token_;
};

grpc加TLS加密和令牌认证相关推荐

  1. docker仓库搭建、加密、用户认证

    1 . 含义及理解: 仓库分为公开仓库(Public)和私有仓库(Private)两种形式.最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载. 国内的公开仓库包括 Docker ...

  2. gRPC 的 SSL/TLS 加密认证

    文章目录 简介 生成自签证证书 服务端 客户端 单向认证 双向认证 基于 Token 的单向认证 基于 Token 的双向认证 简介 传输层安全性协议(Transport Layer Security ...

  3. gRPC教程 — TLS单向认证、双向认证、Token认证、拦截器

    gRPC教程 - 使用TLS时相关问体积解决办法 本文代码 问题重现 解决办法 一.生成CA根证书 1.1 在证书存放文件夹下 新建 `ca.conf`,写入内容如下: 1.2 生成ca秘钥,得到ca ...

  4. 二、Prometheus TLS加密认证和基于 basic_auth 用户名密码访问

    文章目录 Prometheus 基于用户名密码访问 1. `Node Export`端配置密码 2. 在被监控端这里生成密码 3. 在node_exporter中新增配置文件 4. node_expo ...

  5. DM8的TLS加密认证配置相关

    1.为什么要使用SSL/TLS数字证书?   安装了SSL/TLS证书之后,可以保证客户端到服务器端之间的安全通信,数字证书采用非对称加密方式.虽然经过对称加密方式后的数据也无法被破译,但在使用了数字 ...

  6. 基于java注册登录MD5算法加盐加密颁发 Token身份令牌使用各种邮箱发送验证码详解雪花算法

    目的作用 == 在项目中,为了防止别人窥视我们的密码通常我们会采取一些加密方式.这里简单介绍一下MD5 加盐加密方法,MD5叫做信息-摘要算法,严格来说不是加密方式,而是信息摘要. 对于可以接触到数据 ...

  7. Shiro认证及加盐加密

    目录 今天的知识是与上次所分享的知识相关联的,在Shiro入门的基础进行编写,上次之前的数据是死数据(放在Shiro.ini)而这次是活数据,可以连接到数据库,运用域Relam知识.同时出于维护用户的 ...

  8. Openldap配置TLS加密传输(完整版——手动配置)

    首先要实现openLDAP的编译安装以及配置           openLDAP的编译安装以及配置 注意:上篇中的  3. 主配置文件slapd.conf  中  信息如下所示:           ...

  9. 认证令牌_Java应用程序的简单令牌认证

    认证令牌 "我喜欢编写身份验证和授权代码." 〜从来没有Web开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. ...

最新文章

  1. adc 接收cube_官方的stm32cube软件教程实例ADC操作代码(官方自带的,可以无视
  2. office 2007 验证失败的解决方法
  3. How to sign app
  4. php获取当前时间戳方法
  5. 学会了CopyOnWriteArrayList可以再多和面试官对线三分钟
  6. Jmeter通过CSV Data Set Config参数化
  7. android peopleactivity.java,Android面试基础篇---Activity(上)
  8. XML DataBase之Xindice(二)
  9. 网上收集总结一下mssql( 部分)
  10. 算法与数据结构(基于C语言)中线性表的快速排序快速查找
  11. Winform实现读写IC卡Demo源码含注释
  12. [Camera Drv]Factory mode下camera图像rotate了180度 - MTK物联网在线解答 - 技术论坛
  13. Navicat 15 premium手动备份数据库步骤
  14. Nature:每两个月注射一次卡波替格拉韦可以让大多数人免受艾滋病毒
  15. linux下如何使用命令连接wifi
  16. java唯一的id_Java:唯一的10位数ID
  17. Maven的settings.xml配置详解
  18. [图示]做人36字诀:一)社会交往--教你建功立业
  19. 深圳小鹅网络前端校招视频面试
  20. 网贷平台老板跑路,程序员这波操作很溜

热门文章

  1. 超越“虚拟的美丽”——云计算实践再分析
  2. DVWA-文件上传与文件包含
  3. ​微信公众号素材图片去哪找?
  4. Python 获取网站证书有效期
  5. 网络抓包工具wireshark的使用
  6. 想将PPT的文字转换到Word文档?看这一篇就够了!!!
  7. sina 股票接口 2022.1.21 更新
  8. python另存为excel_为什么不能从python代码中“另存为”Excel文件?
  9. 房贷没放款前千万不要做的事
  10. 在Win7上安装TexLive及设置XeLaTeX的整个过程