grpc加TLS加密和令牌认证

栏目: 服务器 · 发布时间: 7年前

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

grpc加TLS加密和令牌认证

(金庆的专栏 2018.11)

用 golang 创建 grpc 服务,开启 TLS 加密,并采用令牌认证。

然后用 C++ 和 golang 分别创建客户端连接服务器。

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
}

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

// 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++ 端的客户端代码比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<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_;
};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Introduction to Linear Optimization

Introduction to Linear Optimization

Dimitris Bertsimas、John N. Tsitsiklis / Athena Scientific / 1997-02-01 / USD 89.00

"The true merit of this book, however, lies in its pedagogical qualities which are so impressive..." "Throughout the book, the authors make serious efforts to give geometric and intuitive explanations......一起来看看 《Introduction to Linear Optimization》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具