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_auth.UnaryServerInterceptor(auth.Authenticate),

grpc_auth.StreamServerInterceptor(auth.Authenticate))

// 注册HelloService

pb.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 auth

import (

"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 on

ctx = 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] = nil

return 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 name

return 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 credentials

creds, 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 later

conn, _ := 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 auth

import (

"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("gamemail.ztgame.com");  // 如果加了 DNS 就不用这个了

auto call_creds = grpc::MetadataCredentialsFromPlugin(

std::unique_ptr<: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<:string grpc::string>* metadata) override {

metadata->insert(std::make_pair("authorization-token", token_));

return grpc::Status::OK;

}

private:

std::string token_;

};

c++客户端 grpc_grpc加TLS加密和令牌认证相关推荐

  1. grpc加TLS加密和令牌认证

    grpc加TLS加密和令牌认证 (金庆的专栏 2018.11) 用 golang 创建 grpc 服务,开启 TLS 加密,并采用令牌认证. 然后用 C++ 和 golang 分别创建客户端连接服务器 ...

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

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

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

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

  4. Openldap配置TLS加密传输(完整版——shell脚本实现[分别在客户端与服务器端执行脚本,实现TLS加密])

    此脚本中只是负责实现了TLS加密配置部分,openLDAP的编译安装以及设置是前期已经配置好的! 具体的配置看上上篇文章openLDAP的编译安装以及配置. 注意slapd.conf中的配置,脚本中为 ...

  5. Openldap配置TLS加密传输(完整版——shell脚本实现[即在客户端执行代码,即可实现TLS加密])

    此脚本中只是负责实现了TLS加密配置部分,openLDAP的编译安装以及设置是前期已经配置好的! 具体的配置看上上篇文章openLDAP的编译安装以及配置. 注意slapd.conf中的配置,脚本中为 ...

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

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

  7. Docker资源控制与TLS加密通信

    文章目录 Docker资源控制 使用stress工作测试cpu和内存 cup周期限制 CPU Core 控制 cpu配额控制参数的混合使用 内存限额 IO限制 bps和iops的限制 Docker-T ...

  8. 开源项目SMSS发开指南(四)——SSL/TLS加密通信详解

    本文将详细介绍如何在Java端.C++端和NodeJs端实现基于SSL/TLS的加密通信,重点分析Java端利用SocketChannel和SSLEngine从握手到数据发送/接收的完整过程.本文也涵 ...

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

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

  10. TLS加密远程连接Docker

    <Docker远程连接设置>一文讲述了开启Docker远程连接的方法,但那种方法不安全,因为任何客户端都可以通过Docker服务的IP地址连接上去,今天我们就来学习Docker官方推荐的安 ...

最新文章

  1. 基于 Ansj 的 elasticsearch 2.3.1 中文分词插件
  2. Scrapy 框架 分布式 爬虫
  3. BadgeView(View上添加提醒)的应用与分析
  4. Codeforces Round #554 (Div. 2) C. Neko does Maths (数论 GCD(a,b) = GCD(a,b-a))
  5. Python老司机总结新手常见10大错误
  6. 同步请求和异步请求的区别
  7. river歌曲表达的意思_华晨宇新歌《斗牛》究竟表达了什么?
  8. HotSpot VM运行时02---VM生命周期
  9. face_recognition 安装报错问题解决
  10. xmind2020激活教程_思维导图软件XMind 2020 v10.2.1中文版的官网下载、安装与序列号注册文件激活教程-推荐实用小软件 -亦是美网络...
  11. oracle报1405,【案例】Oracle报错ORA-15054 asm diskgroup无法mount的解决办法
  12. 零基础怎么学习平面设计*
  13. 嵌入式开发之linux根文件系统移植
  14. [linux]platform总线机制与wtd驱动开发
  15. GMT北京时间表示的时间算法
  16. 怎么用python制作随机点名软件_如何用python编写一个简易的随机点名软件?
  17. 【经营智慧】004.做一个善于发挥自己才智的人
  18. map_server
  19. 华为认证存储方向冷门吗?这个存储证考下来有啥用?
  20. linux3.0操作系统下载,GNOME下载3.0 正式版_新一代桌面用户界面的Linux操作系统下载...

热门文章

  1. http://blog.sina.com.cn/s/blog_6a01140c0100wimi.html
  2. jdbc:initialize-database标签的研究
  3. SQL导入/导出Excel
  4. 一个简单的登陆功能模块
  5. .net中序列化读写xml方法的总结
  6. 格雷码 Gray Code
  7. Python-判断变量类型和继承链-type isinstance
  8. 【VirtualBox】设置NAT端口映射-SSH登录
  9. Linux下svn的部署
  10. 数据结构笔记-----链表