1、认证方式
gRPC默认内置了两种认证方式:
SSL/TLS 认证方式
ALTS 认证方式(暂略)
基于 Token 的认证方式
同时,gRPC提供了接口用于扩展自定义认证方式。
2、TLS认证示例
这里直接扩展hello项目,实现TLS认证机制
首先需要准备证书,在根目录新建keys目录用于存放证书文件。
golang 1.15+版本上,用 gRPC通过TLS实现数据传输加密时,证书需要开启SAN扩展(默认是没有开启SAN扩展的)
2.1 ca根证书生成
新建ca.conf,填入以下内容:
[ req ]
default_bits = 4096
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 = GuangDong
localityName = Locality Name (eg, city)
localityName_default = ShenZhen
organizationName = Organization Name (eg, company)
organizationName_default = top
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = www.admincms.com // XXX(自定义)
生成ca.key:
openssl genrsa -out ca.key 4096
生成ca.csr:(直接回车,采用default默认配置值)
openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf
生成ca.crt:
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
2.2 server证书生成
在keys目录下,新建server.conf,填入以下内容:
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = GuangDong
localityName = Locality Name (eg, city)
localityName_default = ShenZhen
organizationName = Organization Name (eg, company)
organizationName_default = Sheld
commonName = CommonName (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = www.admincms.com // 自定义,客户端需要此字段做匹配
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = www.admincms.com // 自定义,可以还可以创建dns.2 ... dns.n
IP = 127.0.0.1
生成server.key:
openssl genrsa -out server.key 2048
生成server.csr:(直接回车,采用default默认配置值)
openssl req -new -sha256 -out server.csr -key server.key -config server.conf
生成server.crt:
openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extensions req_ext -extfile server.conf
2.3 服务端代码示例
// 地址
func main(){
addr := "127.0.0.1:8180"
// 1.监听
listener, err := net.Listen("tcp", addr)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
fmt.Printf("监听端口:%s\n", addr)
// tls认证
creds,err := credentials.NewServerTLSFromFile("./framework/grpc/keys/server.pem", "./framework/grpc/keys/server.key")
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}
// 2.实例化gRPC
//s := grpc.NewServer()
s := grpc.NewServer(grpc.Creds(creds))
// 3.在gRPC上注册微服务
var u = UserInfoService{}
pb.RegisterUserInfoServiceServer(s, &u)
var h = HelloService{}
pb.RegisterHelloServiceServer(s, &h)
grpclog.Infoln("Listen on " + addr + " with TLS")
// 4.启动服务端
s.Serve(listener)
}
运行:
API server listening at: 127.0.0.1:12044
监听端口:127.0.0.1:8180
2.4 客户端代码示例
func main(){
addr := "127.0.0.1:8180"
// TLS连接 记得把server name改成你写的服务器地址
creds, err := credentials.NewClientTLSFromFile("./framework/grpc/keys/server.pem", "www.admincms.com")
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
// 1.连接
//conn, err := grpc.Dial(addr, grpc.WithInsecure())
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 2. 实例化gRPC客户端
client := pb.NewUserInfoServiceClient(conn)
// 3.组装请求参数
req := new(pb.UserRequest)
req.Name = "zs"
// 4. 调用接口
response, err := client.GetUserInfo(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
fmt.Printf("响应结果: %v\n", response)
// 客户端调用HelloService
hClient := pb.NewHelloServiceClient(conn)
res1,err := hClient.SayHello(context.Background(), &pb.HelloRequest{
Name: "grpc",
})
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println("响应结果:", res1)
}
运行:
API server listening at: 127.0.0.1:12105
响应结果: id:1 name:"zs" age:22 hobby:"Sing" hobby:"Run"
响应结果: result:"hello grpc"
3、Token认证示例
再进一步,继续扩展hello-tls项目,实现 TLS + Token 认证机制
3.1 客户端代码示例
package main
import (
"context"
"fmt"
"go_study/framework/grpc/pb" // 导入proto
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
)
// OpenTLS 是否开启TLS认证
var OpenTLS = true
type customCredential struct {}
func (c *customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
func (c *customCredential) RequireTransportSecurity() bool {
return OpenTLS
}
func main(){
addr := "127.0.0.1:8180"
var err error
var opts []grpc.DialOption
if OpenTLS {
// TLS连接 记得把server name改成你写的服务器地址
creds, err := credentials.NewClientTLSFromFile("./framework/grpc/keys/server.pem", "www.admincms.com")
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
}else{
opts = append(opts, grpc.WithInsecure())
}
// 使用自定义认证
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
// 1.连接
conn, err := grpc.Dial(addr, opts...)
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 2. 实例化gRPC客户端
client := pb.NewUserInfoServiceClient(conn)
// 3.组装请求参数
req := new(pb.UserRequest)
req.Name = "zs"
// 4. 调用接口
response, err := client.GetUserInfo(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
fmt.Printf("响应结果: %v\n", response)
// 客户端调用HelloService
hClient := pb.NewHelloServiceClient(conn)
res1,err := hClient.SayHello(context.Background(), &pb.HelloRequest{
Name: "grpc",
})
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println("响应结果:", res1)
}
这里定义了一个customCredential
结构体,并实现了两个方法GetRequestMetadata
和RequireTransportSecurity
。
这是gRPC提供的自定义认证方式,每次RPC调用都会传输认证信息。
customCredential
其实是实现了 grpc/credential
包内的 PerRPCCredentials
接口。
每次调用,token信息会通过请求的 metadata 传输到服务端。
下面具体看一下服务端如何获取 metadata 中的信息。
3.2 服务端代码示例
修改server/main.go中的SayHello
方法(如有包含其他服务,也需一并修改)。
package main
import (
"context"
"fmt"
"go_study/framework/grpc/pb" // 导入proto
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"net"
)
// 定义 UserInfoService 服务
type UserInfoService struct{
pb.UnimplementedUserInfoServiceServer
}
// GetUserInfo 实现方法
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
err = tokenAuth(ctx)
if err != nil {
return nil, err
}
// 通过用户名查询用户信息
name := req.Name
// 数据里查用户信息
if name == "zs" {
resp = &pb.UserResponse{
Id: 1,
Name: name,
Age: 22,
Hobby: []string{"Sing", "Run"},
}
}
return
}
type HelloService struct {
pb.UnimplementedHelloServiceServer
}
// 使用这种方式验证token 每个服务端每个服务中都验证一次,比较繁琐
func tokenAuth(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Errorf(codes.Unauthenticated, "无Token认证信息")
}
var (
appid string
appkey string
)
if val, ok := md["appid"]; ok {
appid = val[0]
}
if val, ok := md["appkey"]; ok {
appkey = val[0]
}
if appid != "101010" || appkey != "i am key" {
return status.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
}
return nil
}
func (h *HelloService) SayHello(ctx context.Context, in *pb.HelloRequest) (resp *pb.HelloResponse, err error) {
err = tokenAuth(ctx)
if err != nil {
return nil, err
}
resp = new(pb.HelloResponse)
resp.Result = fmt.Sprintf("hello %s", in.Name)
return resp, nil
}
func main(){
// 地址
addr := "127.0.0.1:8180"
// 1.监听
listener, err := net.Listen("tcp", addr)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
fmt.Printf("监听端口:%s\n", addr)
// tls认证
creds,err := credentials.NewServerTLSFromFile("./framework/grpc/keys/server.pem", "./framework/grpc/keys/server.key")
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}
// 2.实例化gRPC
//s := grpc.NewServer()
s := grpc.NewServer(grpc.Creds(creds))
// 3.在gRPC上注册微服务
var u = UserInfoService{}
pb.RegisterUserInfoServiceServer(s, &u)
var h = HelloService{}
pb.RegisterHelloServiceServer(s, &h)
grpclog.Infoln("Listen on " + addr + " with TLS")
// 4.启动服务端
s.Serve(listener)
}
服务端可以从context
中获取每次请求的metadata,从中读取客户端发送的token信息并验证有效性。
使用这种方式验证token 每个服务端每个服务中都验证一次,比较繁琐。(下一篇介绍拦截器)
运行服务端:
再运行客户端:
google.golang.org/grpc/credentials/oauth
包已实现了用于Google API的oauth和jwt验证的方法,使用方法可以参考官方文档。
在实际应用中,我们可以根据自己的业务需求实现合适的验证方式。
最后编辑:joker.liu 更新时间:2023-05-23 14:46