详细解析下gRPC examples-authenication

详细解析下gRPC examples-authenication

认证

在 gRPC 中,认证被抽象为 credentials.PerRPCCredentials。通常,它还包括授权。用户可以在每个连接或每个调用的基础上进行配置。

目前,认证的示例包括使用 OAuth2 与 gRPC 的示例。

尝试

go run server/main.go

go run client/main.go

解释

OAuth2

OAuth 2.0 协议是目前广泛使用的身份验证和授权机制。gRPC 提供了方便的 API 来配置 OAuth 以与 gRPC 一起使用。请参考 godoc:https://godoc.org/google.golang.org/grpc/credentials/oauth 了解详细信息。

客户端

在客户端上,用户首先应获取有效的 OAuth 令牌,然后初始化一个实现了 credentials.PerRPCCredentialsoauth.TokenSource。接下来,如果用户希望为同一连接上的所有 RPC 调用应用单个 OAuth 令牌,然后使用 grpc Dial 配置 DialOption WithPerRPCCredentials。或者,如果用户希望为每个调用应用 OAuth 令牌,然后使用 grpc RPC 调用配置 CallOption PerRPCCredentials

请注意,OAuth 要求底层传输层是安全的(例如,TLS 等)。

在 gRPC 内部,提供的令牌前缀为令牌类型和一个空格,然后附加到带有键 “authorization” 的元数据中。

客户端实例
/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// 该客户端演示了如何为每个RPC提供OAuth2令牌。
package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "time"

    "golang.org/x/oauth2"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/oauth"
    "google.golang.org/grpc/examples/data"
    ecpb "google.golang.org/grpc/examples/features/proto/echo"
)

var addr = flag.String("addr", "localhost:50051", "the address to connect to")

// callUnaryEcho 调用一元 RPC 函数并处理响应。
func callUnaryEcho(client ecpb.EchoClient, message string) {

    // 创建一个带有超时的上下文(Context),以便在超过指定的时间后自动取消操作。
    // context.Background() 创建一个没有任何父上下文的根上下文。
    // 10*time.Second 表示超时时间为10秒。
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

    // 使用 defer 关键字,以确保在函数结束时调用 cancel 函数来取消上下文。
    defer cancel()

    // 使用客户端对象 client 调用一元 RPC 函数 UnaryEcho,并传递上下文 ctx 和请求参数。
    // 在此示例中,我们要发送一条消息给服务器,消息内容为 message 变量的值。
    resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})

    // 检查是否发生了错误。
    if err != nil {
        // 如果发生错误,使用 log.Fatalf 函数记录错误信息并退出程序。
        log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
    }

    // 如果没有发生错误,打印从服务器返回的响应消息。
    fmt.Println("UnaryEcho: ", resp.Message)

}

func main() {
    flag.Parse()

    // 设置连接的凭证。
    perRPC := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(fetchToken())}
    creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
    if err != nil {
        log.Fatalf("failed to load credentials: %v", err)
    }
    opts := []grpc.DialOption{
        // 除了下面的 grpc.DialOption,调用者还可以在 RPC 调用中使用 grpc.CallOption grpc.PerRPCCredentials。
        // 参见:https://godoc.org/google.golang.org/grpc#PerRPCCredentials
        grpc.WithPerRPCCredentials(perRPC),
        // oauth.TokenSource 需要配置传输凭证。
        grpc.WithTransportCredentials(creds),
    }

    conn, err := grpc.Dial(*addr, opts...)
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    rgc := ecpb.NewEchoClient(conn)

    callUnaryEcho(rgc, "hello world")
}

// fetchToken 模拟令牌查找并省略正确令牌获取的详细信息。
// 有关获取OAuth2令牌的示例,请参见:
// https://godoc.org/golang.org/x/oauth2
func fetchToken() *oauth2.Token {
    return &oauth2.Token{
        AccessToken: "some-secret-token",
    }
}
服务端实例

在服务器端,用户通常在拦截器内部获取令牌并进行验证。要获取令牌,请调用 metadata.FromIncomingContext 并传入给定的上下文。它将返回元数据映射。接下来,使用键 “authorization” 获取相应的值,该值是字符串切片。对于 OAuth,切片应只包含一个元素,该元素是格式为 <令牌类型> + " " + <令牌> 的字符串。用户可以通过解析字符串轻松获取令牌,然后验证其有效性。

如果令牌无效,则返回带有错误代码 codes.Unauthenticated 的错误。

如果令牌有效,则调用方法处理程序以开始处理 RPC。

服务端代码实例
/*
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// 服务端演示了每个rpc方法怎样去消耗和验证客户端提供的OAuth2 tokens
package main

import (
    "context"
    "crypto/tls"
    "flag"
    "fmt"
    "log"
    "net"
    "strings"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/examples/data"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/status"

    pb "google.golang.org/grpc/examples/features/proto/echo"
)

var (
    // 此处定义全局错误,表示无法解析主要数据
    errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
    // 此处定义全局错误,报错token无效
    errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
)

// 命令行选项,默认为50051端口,监听服务
var port = flag.Int("port", 50051, "the port to serve on")

func main() {
    flag.Parse()
    fmt.Printf("server starting on port %d...\n", *port)
    /*
          cert, err := ...:这行代码声明了两个变量 cert 和 err,并使用 := 运算符初始化它们。这意味着它在加载证书和密钥对后会分别接收这两个值。

        tls.LoadX509KeyPair(...):这是一个TLS包中的函数,用于加载X.509证书和私钥对。X.509是一种公钥基础设施(PKI)标准,用于数字证书的格式。

        data.Path("x509/server_cert.pem") 和 data.Path("x509/server_key.pem"):这两个参数是证书文件和私钥文件的路径。它们使用了一个函数 data.Path() 来获取文件路径,这个函数可能是在代码的其他地方定义的,用于获取文件的实际路径。

        总结一下,这行代码的作用是从指定的文件路径加载服务器端的X.509证书和私钥对,这些证书和密钥对将用于服务器端的安全通信,以确保通信的机密性和完整性。如果加载成功,cert 变量将包含证书和私钥对的信息,否则 err 变量将包含加载过程中的错误信息。这是建立安全gRPC服务器连接所必需的步骤之一。
    */
    cert, err := tls.LoadX509KeyPair(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
    if err != nil {
        log.Fatalf("failed to load key pair: %s", err)
    }
    opts := []grpc.ServerOption{
        // 下面的 grpc.ServerOption 为所有一元RPC添加了一个拦截器。
        // 若要为流式RPC配置拦截器,请参见:
        // https://godoc.org/google.golang.org/grpc#StreamInterceptor
        grpc.UnaryInterceptor(ensureValidToken),
        // 为所有传入连接启用TLS。
        grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
    }
    s := grpc.NewServer(opts...)
    pb.RegisterEchoServer(s, &ecServer{})
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

type ecServer struct {
    pb.UnimplementedEchoServer
}

func (s *ecServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
    return &pb.EchoResponse{Message: req.Message}, nil
}

// valid 验证授权是否有效。
func valid(authorization []string) bool {
    if len(authorization) < 1 {
        return false
    }
    token := strings.TrimPrefix(authorization[0], "Bearer ")
    // 在这里执行令牌验证。出于本示例的目的,此处的代码放弃了通常的OAuth2令牌验证,而是检查与任意字符串匹配的令牌。
    return token == "some-secret-token"
}

// ensureValidToken 确保请求的元数据中存在有效的令牌。如果令牌缺失或无效,拦截器会阻止处理程序的执行并返回错误。
// 否则,拦截器将调用一元处理程序。
func ensureValidToken(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, errMissingMetadata
    }
    // metadata.MD 中的键已规范化为小写。
    // 参见:https://godoc.org/google.golang.org/grpc/metadata#New
    if !valid(md["authorization"]) {
        return nil, errInvalidToken
    }
    // 在确保存在有效令牌后继续执行处理程序。
    return handler(ctx, req)
}

思维导图

详细解析下gRPC examples-authenication_第1张图片

你可能感兴趣的:(grpc,grpc,go)