【grpc】grpc进阶二,grpc认证方式

本章把之前的工程结构改了一下,创建了 server 和 client 两个目录,分别把 server.go,client.go 移动过去。
接下来会介绍 grpc 的 TLS 认证和 Oauth2

一、TLS认证

在进行功能验证是需要使用 openssl 创建自有证书,下面是创建步骤。
创建 ca 证书:

cat > openssl.cnf << EOF
copy_extensions = copy
 
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
 
[req_distinguished_name]
# 国家
C = CN
# 省份
ST = BeiJing
# 城市
L = BeiJing
# 组织
O = grpcdemo
# 部门
OU = grpcdemo
# 域名
CN = api.grpcdemo.com
 
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
 
[alt_names]
# 解析域名
DNS.1 = *.api.grpcdemo.com
# 可配置多个域名,如下
DNS.2 = *.grpcdemo.com
EOF

openssl genrsa -des3 -out ca.key 2048  # 回车后需要输入两次密码,任意即可,123456
openssl req -new -key ca.key -out ca.csr # 回车后需要输入一些信息,如下所示
# Enter pass phrase for ca.key:123456
# You are about to be asked to enter information that will be incorporated
# into your certificate request.
# What you are about to enter is what is called a Distinguished Name or a DN.
# There are quite a few fields but you can leave some blank
# For some fields there will be a default value,
# If you enter '.', the field will be left blank.
# -----
# Country Name (2 letter code) [AU]:CN
# State or Province Name (full name) [Some-State]:BeiJing
# Locality Name (eg, city) []:BeiJing
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:grpcdemo
# Organizational Unit Name (eg, section) []:grpcdemo
# Common Name (e.g. server FQDN or YOUR name) []:api.grpcdemo.com
# Email Address []:grpcdemo@grpc.com

# Please enter the following 'extra' attributes
# to be sent with your certificate request
# A challenge password []:直接回车
# An optional company name []:直接回车

openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt # 这边也需要验证刚才的密码,123456

创建 server 证书

mkdir server
openssl genpkey -algorithm RSA -out server/server.key
openssl req -new -nodes -key server/server.key -out server/server.csr -config openssl.cnf -extensions v3_req
openssl x509 -req -in server/server.csr -out server/server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req # 需要验证密码,123456

创建完成后的文件路径如下:

.
├── ca.crt
├── ca.csr
├── ca.key
├── ca.srl
├── openssl.cnf
└── server
    ├── server.csr
    ├── server.key
    └── server.pem

接下来把 server/server.key 和 server/server.pem 放到我们的代码工程里去,现在的代码工程结构为:

.
├── calc
│   ├── calc.pb.go
│   ├── calcRequest.pb.go
│   ├── calcServer.go
│   └── calc_grpc.pb.go
├── cert
│   ├── server.key
│   └── server.pem
├── client
│   └── client.go
├── go.mod
├── go.sum
├── log
│   ├── grpc.log -> grpc.log.20240413
│   └── grpc.log.20240413
├── main.go
├── proto
│   ├── calc.proto
│   └── calcRequest.proto
└── server
    └── server.go

修改客户端代码

// client.go

package client

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcDemo/calc"
)

func Test(addr string) {
	// 注意:这里使用的是服务端证书和证书中的名称
	creds, err := credentials.NewClientTLSFromFile("./cert/server.pem", "api.grpcdemo.com")
	if err != nil {
		panic(err)
	}
	// 创建rpc连接
	cc, err := grpc.NewClient(addr,
		grpc.WithTransportCredentials(creds))
	if err != nil {
		panic(err)
	}
	defer cc.Close()
	// 创建rpc调用客户端
	cli := calc.NewCalcClient(cc)
	// 调用具体的rpc方法
	versionRsp, err := cli.Version(context.Background(), &calc.Empty{})
	if err != nil {
		panic(err)
	}
	fmt.Println("server version:", versionRsp.GetStr())

	sumRsp, err := cli.Sum(context.Background(), &calc.CalcRequest{
		A: 1,
		B: 2,
	})
	fmt.Println("1+2=", sumRsp.GetNum())
}

修改服务端代码

// server.go

package server

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcDemo/calc"
	"net"
	"time"
)

func Start(addr string) {
	// 监听端口
	ls, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer ls.Close()
	
	creds, err := credentials.NewServerTLSFromFile("./cert/server.pem", "./cert/server.key")
	if err != nil {
		panic(err)
	}
	// 创建grpc服务
	gServer := grpc.NewServer(grpc.Creds(creds))

	// 注册rpc
	calc.RegisterCalcServer(gServer, &calc.Server{})
	if err = gServer.Serve(ls); err != nil {
		panic(err)
	}
}

二、Oauth2认证

这里以基础使用为例子,oauth2也支持的认证方法比较多,比如 jwt 之类的,大家可以自行探索下。

2.1. grpc 客户端

grpc 客户端提供了为每个 rpc 连接进行认证的配置。

func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption {
	return newFuncDialOption(func(o *dialOptions) {
		o.copts.PerRPCCredentials = append(o.copts.PerRPCCredentials, creds)
	})
}

google.golang.org/grpc/credentials/oauth 包里的 TokenSource 类型实现了 credentials.PerRPCCredentials的接口。

type TokenSource struct {
	oauth2.TokenSource
}

golang.org/x/oauth2 包中的 TokenSource 如下:

type TokenSource interface {
	// Token returns a token or an error.
	// Token must be safe for concurrent use by multiple goroutines.
	// The returned Token must not be modified.
	Token() (*Token, error)
}

// ...
func StaticTokenSource(t *Token) TokenSource {
	return staticTokenSource{t}
}

// ...

type Token struct {
	// AccessToken is the token that authorizes and authenticates
	// the requests.
	AccessToken string `json:"access_token"`

	// TokenType is the type of token.
	// The Type method returns either this or "Bearer", the default.
	TokenType string `json:"token_type,omitempty"`

	// RefreshToken is a token that's used by the application
	// (as opposed to the user) to refresh the access token
	// if it expires.
	RefreshToken string `json:"refresh_token,omitempty"`

	// Expiry is the optional expiration time of the access token.
	//
	// If zero, TokenSource implementations will reuse the same
	// token forever and RefreshToken or equivalent
	// mechanisms for that TokenSource will not be used.
	Expiry time.Time `json:"expiry,omitempty"`

	// raw optionally contains extra metadata from the server
	// when updating a token.
	raw interface{}

	// expiryDelta is used to calculate when a token is considered
	// expired, by subtracting from Expiry. If zero, defaultExpiryDelta
	// is used.
	expiryDelta time.Duration
}

所以我们修改客户端代码如下:

// client.go

package client

import (
	"context"
	"fmt"
	"golang.org/x/oauth2"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
	"grpcDemo/calc"
)

func Test(addr string) {
	// 注意:这里使用的是服务端证书和证书中的名称
	creds, err := credentials.NewClientTLSFromFile("./cert/server.pem", "api.grpcdemo.com")
	if err != nil {
		panic(err)
	}

	// 创建rpc连接
	cc, err := grpc.NewClient(addr,
		grpc.WithTransportCredentials(creds),
		// 默认bearer token
		grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{
			AccessToken: "grpc-demo-token",
		})}))
	if err != nil {
		panic(err)
	}
	defer cc.Close()
	// 创建rpc调用客户端
	cli := calc.NewCalcClient(cc)
	// 调用具体的rpc方法
	versionRsp, err := cli.Version(context.Background(), &calc.Empty{})
	if err != nil {
		panic(err)
	}
	fmt.Println("server version:", versionRsp.GetStr())

	sumRsp, err := cli.Sum(context.Background(), &calc.CalcRequest{
		A: 1,
		B: 2,
	})
	fmt.Println("1+2=", sumRsp.GetNum())
}

2.2. grpc 服务端

这里我们介绍下两个知识点

  • grpc 元数据

grpc 是基于 HTTP/2 进行通信的,这个元数据类似于 http header,类型定义为type MD map[string][]string。在服务端可以用 metadata.FromIncomingContext获取元数据,还有个封装好的方法metadata.ValueFromIncomingContext可以获取某个指定 key 的元数据。

  • grpc 拦截器

grpc 服务端创建时,提供 grpc.UnaryInterceptor 选项可以配置非流式 rcp 的拦截器,具体使用比较简单,这里不赘述了。

下面看下服务端代码:

// server.go

package server

import (
	"context"
	"errors"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"grpcDemo/calc"
	"net"
)

func interceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
	tokens := metadata.ValueFromIncomingContext(ctx, "authorization")
	if len(tokens) == 0 {
		return nil, errors.New("invalid token")
	}
	fmt.Println("get token:", tokens[0])
	return handler(ctx, req)
}

func Start(addr string) {
	// 监听端口
	ls, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer ls.Close()

	creds, err := credentials.NewServerTLSFromFile("./cert/server.pem", "./cert/server.key")
	if err != nil {
		panic(err)
	}
	// 创建grpc服务
	gServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))

	// 注册rpc
	calc.RegisterCalcServer(gServer, &calc.Server{})
	if err = gServer.Serve(ls); err != nil {
		panic(err)
	}
}

运行结果如下:
在这里插入图片描述

三、自定义认证

上面可以看到,我们在进行认证配置的时候使用的是grpc.WithPerRPCCredentials,所以我们实现credentials.PerRPCCredentials类型的接口即可自定义认证内容。

// PerRPCCredentials defines the common interface for the credentials which need to
// attach security information to every RPC (e.g., oauth2).
type PerRPCCredentials interface {
	// GetRequestMetadata gets the current request metadata, refreshing tokens
	// if required. This should be called by the transport layer on each
	// request, and the data should be populated in headers or other
	// context. If a status code is returned, it will be used as the status for
	// the RPC (restricted to an allowable set of codes as defined by gRFC
	// A54). uri is the URI of the entry point for the request.  When supported
	// by the underlying implementation, ctx can be used for timeout and
	// cancellation. Additionally, RequestInfo data will be available via ctx
	// to this call.  TODO(zhaoq): Define the set of the qualified keys instead
	// of leaving it as an arbitrary string.
	GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
	// RequireTransportSecurity indicates whether the credentials requires
	// transport security.
	RequireTransportSecurity() bool
}

下面我们在项目中创建 auth 目录,并创建 auth.go 的文件:

// auth.go
package auth

import (
	"context"
	"errors"
	"google.golang.org/grpc/metadata"
)

type GrpcDemoAuth struct {
	Ak, Sk string // 自定义认证内容
	UseTls bool   // 是否进行使用tls加密
}

func (g GrpcDemoAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{"ak": g.Ak, "sk": g.Sk}, nil
}

func (g GrpcDemoAuth) RequireTransportSecurity() bool {
	return g.UseTls
}

// 从元数据中构建认证token
func GetFromCtx(ctx context.Context) (GrpcDemoAuth, error) {
	g := GrpcDemoAuth{}
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return g, errors.New("metadata nil")
	}
	aks := md.Get("ak")
	sks := md.Get("sk")
	if len(aks) == 0 || len(sks) == 0 {
		return g, errors.New("token error")
	}
	g.Ak = aks[0]
	g.Sk = sks[0]
	return g, nil
}

客户端实现:

// client.go

package client

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcDemo/auth"
	"grpcDemo/calc"
)

func Test(addr string) {
	// 注意:这里使用的是服务端证书和证书中的名称
	creds, err := credentials.NewClientTLSFromFile("./cert/server.pem", "api.grpcdemo.com")
	if err != nil {
		panic(err)
	}

	// 创建rpc连接
	cc, err := grpc.NewClient(addr,
		grpc.WithTransportCredentials(creds),
		// 默认bearer token
		grpc.WithPerRPCCredentials(&auth.GrpcDemoAuth{
			Ak:     "grpc-demo-ak",
			Sk:     "grpc-demo-sk",
			UseTls: true,
		}))
	if err != nil {
		panic(err)
	}
	defer cc.Close()
	// 创建rpc调用客户端
	cli := calc.NewCalcClient(cc)
	// 调用具体的rpc方法
	versionRsp, err := cli.Version(context.Background(), &calc.Empty{})
	if err != nil {
		panic(err)
	}
	fmt.Println("server version:", versionRsp.GetStr())

	sumRsp, err := cli.Sum(context.Background(), &calc.CalcRequest{
		A: 1,
		B: 2,
	})
	fmt.Println("1+2=", sumRsp.GetNum())
}

服务端实现:

// server.go

package server

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"grpcDemo/auth"
	"grpcDemo/calc"
	"net"
)

func interceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
	authToken, err := auth.GetFromCtx(ctx)
	if err != nil {
		return nil, err
	}
	fmt.Printf("get token ak:%s, sk:%s\n", authToken.Ak, authToken.Sk)
	return handler(ctx, req)
}

func Start(addr string) {
	// 监听端口
	ls, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer ls.Close()

	creds, err := credentials.NewServerTLSFromFile("./cert/server.pem", "./cert/server.key")
	if err != nil {
		panic(err)
	}
	// 创建grpc服务
	gServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))

	// 注册rpc
	calc.RegisterCalcServer(gServer, &calc.Server{})
	if err = gServer.Serve(ls); err != nil {
		panic(err)
	}
}

运行结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/542562.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Paddle实现人脸对比(二)

我之前发过一篇基于孪生网络的人脸对比的文章&#xff0c;这篇文章也到了百度的推荐位置&#xff1a; 但是&#xff0c;效果并不是很好。经过大量的搜索&#xff0c;我发现了一种新的方法&#xff0c;可以非常好的实现人脸对比。 原理分析 我们先训练一个普通的人脸分类模型&…

关于机器学习/深度学习的一些事-答知乎问(二)

进化算法与深度强化学习算法结合如何进行改进&#xff1f; &#xff08;1&#xff09;进化算法普遍存在着样本效率低下的问题&#xff0c;虽然其探索度较高&#xff0c;但其本质为全局随机性搜索&#xff0c;需要在整个回合结束后才能更新其种群&#xff0c;而深度强化学习在每…

深入理解计算机网络分层结构

一、 为什么要分层&#xff1f; 计算机网络分层的主要目的是将复杂的网络通信过程分解为多个相互独立的层次&#xff0c;每个层次负责特定的功能。这样做有以下几个好处&#xff1a; 模块化设计&#xff1a;每个层次都有清晰定义的功能和接口&#xff0c;使得网络系统更易于设…

023——搭建图形化客户端(基于pySimpleGUI)

目录 一、pysimplegui 1.1 安装 1.2 测试 二、 pysimplegui学习 2.1 学习地址 2.2 人类早期驯服pysimplegui珍贵流水账 三、 实现项目专属的界面 一、pySimpleGUI 1.1 安装 pip install pysimplegui -i https://pypi.tuna.tsinghua.edu.cn/simple Command pip not fo…

GAN:对抗生成网络【通俗易懂】

一、概述 对抗生成网络&#xff08;GAN&#xff09;是一种深度学习模型&#xff0c;由两个神经网络组成&#xff1a;生成器G和判别器D。这两个网络被训练来协同工作&#xff0c;以生成接近真实数据的新样本。 生成器的任务是接收一个随机噪声向量&#xff0c;并将其转换为与真…

【Web】DASCTF X GFCTF 2022十月挑战赛题解

目录 EasyPOP hade_waibo EasyLove BlogSystem EasyPOP 先读hint.php sorry.__destruct -> secret_code::secret() exp: $anew sorry(); $bnew secret_code(); $a->password"suibian"; $a->name"jay"; echo serialize($a); 真暗号啊&…

基于Java停车场管理系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

即插即用模块之DO-Conv(深度过度参数化卷积层)详解

目录 一、摘要 二、核心创新点 三、代码详解 四、实验结果 4.1Image Classification 4.2Semantic Segmentation 4.3Object Detection 五、总结 论文&#xff1a;DOConv论文 代码&#xff1a;DOConv代码 一、摘要 卷积层是卷积神经网络(cnn)的核心组成部分。在本文中…

【Java虚拟机】简单易懂的ZGC原理分析

简单易懂的ZGC原理分析 GC垃圾收集器ZGC的特点ZGC相关技术Region染色指针 & 转发表 & 读屏障染色指针转发表读屏障 内存多重映射 ZGC流程详解ZGC与其他垃圾搜集器比较与CMS比较与G1比较 GC垃圾收集器 GC垃圾收集器的作用就是帮我们清理堆内存里面的垃圾&#xff0c;无…

第1章、react基础知识;

一、react学习前期准备&#xff1b; 1、基本概念&#xff1b; 前期的知识准备&#xff1a; 1.javascript、html、css&#xff1b; 2.构建工具&#xff1a;Webpack&#xff1a;https://yunp.top/init/p/v/1 3.安装node&#xff1a;npm&#xff1a;https://yunp.top/init/p/v/1 …

cmake制作并链接动静态库

cmake制作并链接动静态库 制作静态库add_library(库名称 STATIC 源文件1 [源文件2] ...)LIBRARY_OUTPUT_PATH指定库的生成路径 制作动态库add_library(库名称 SHARED 源文件1 [源文件2] ...) 连接动静态库link_libraries连接静态库link_directories到哪个路径去找库target_link…

UnityShader学习计划

1.安装ShaderlabVS,vs的语法提示 2. 常规颜色是fixed 3.FrameDebugger调试查看draw的某一帧的全部信息&#xff0c;能看到变量参数的值

雅马哈电钢琴YDP145

数据线&#xff1a;MIDI 琴可以通过MIDI、线直接连接手机&#xff0c;播放声音 琴通过线连接电脑&#xff0c;不能直接播放声音 https://www.bilibili.com/video/BV1ws4y1M7yw 操作&#xff1a; https://usa.yamaha.com/support/updates/yamaha_steinberg_usb_driver_for_win…

王道汽车4S企业管理系统 SQL注入漏洞复现

0x01 产品简介 王道汽车4S企业管理系统(以下简称“王道4S系统”)是一套专门为汽车销售和维修服务企业开发的管理软件。该系统是博士德软件公司集10余年汽车行业管理软件研发经验之大成,精心打造的最新一代汽车4S企业管理解决方案。 0x02 漏洞概述 王道汽车4S企业管理系统…

完美照片由构图决定,摄影构图基础到进阶

一、资料描述 本套摄影构图资料&#xff0c;大小1.04G&#xff0c;共有51个文件。 二、资料目录 新手必备-摄影构图技巧.doc 无忌版的《摄影构图学》.pdf 完美照片的十大经典拍摄技法.pdf 数码摄影曝光手边书.pdf 数码摄影不求人.30天学会数码摄影构图.pdf 数码单反摄影…

sheng的学习笔记-AI-决策树(Decision Tree)

AI目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 目录 什么是决策树 划分选择 信息增益 增益率 基尼指数 剪枝处理 预剪枝 后剪枝 连续值处理 另一个例子 基本步骤 排序 计算候选划分点集合 评估分割点 每个分割点都进行评估&#xff0c;找到最大信息增益的…

靠谱的大型相亲交友婚恋平台有哪些?相亲app软件前十名

靠谱交友软件&#xff0c;个人感觉还是要选择大型的&#xff0c;口碑好的进行选择&#xff0c;以下是我用过的婚恋平台&#xff0c;分享给大家 1、丛丛 这是我用的最久的一款脱单小程序&#xff0c;我老公就是在这个小程序找到的&#xff01;&#xff01;&#xff01; 这是一款…

CSS边框

目录 内容区&#xff08;content&#xff09;&#xff1a; 边框&#xff08;border&#xff09;&#xff1a; 前言&#xff1a; 示例&#xff1a; 内容区&#xff08;content&#xff09;&#xff1a; 内容区就是盒子里面用来存放东西的区域&#xff0c;里面你可以随便放如:…

计算机三级数据库技术备考笔记(十三)

第十三章 大规模数据库架构 分布式数据库 分布式数据库系统概述 分布式数据库系统是物理上分散、逻辑上集中的数据库系统。系统中的数据分布在物理位置不同的计算机上&#xff08;通常称为场地、站点或结点&#xff0c;本章均用场地来描述&#xff09;&#xff0c;由通信网络将…

大语言模型总结整理(不定期更新)

《【快捷部署】016_Ollama&#xff08;CPU only版&#xff09;》 介绍了如何一键快捷部署Ollama&#xff0c;今天就来看一下受欢迎的模型。 模型简介gemmaGemma是由谷歌及其DeepMind团队开发的一个新的开放模型。参数&#xff1a;2B&#xff08;1.6GB&#xff09;、7B&#xff…