双端实现流式拦截器与 OAuth2 认证的逻辑步骤汇总
步骤 | 客户端逻辑 | 服务端逻辑 |
---|---|---|
1. 获取令牌 | - 客户端通过 FetchToken 函数获取令牌,通常为 OAuth2 的访问令牌(Access Token)。 | - 服务端通过静态配置或动态服务获取合法的令牌(如 fetchToken() ),作为验证基准。 |
2. 创建认证凭据 | - 使用令牌调用 GetPerRPCCredentials 封装成 credentials.PerRPCCredentials 。 | - 在请求到达服务端时,从元数据中提取 Authorization 字段并解析令牌内容。 |
3. 附加认证元数据 | - 在客户端流式拦截器中,判断是否已配置凭据,若没有,则通过 grpc.PerRPCCredentials 附加认证信息。 | - 在服务端流式拦截器中,从流上下文(ServerStream.Context )中提取元数据,用于后续校验。 |
4. 配置拦截器 | - 设置流式拦截器 StreamInterceptor ,在每个流式 RPC 请求中附加认证信息。 | - 设置服务端流式拦截器 StreamInterceptor ,在每个流式请求到达时校验身份认证信息。 |
5. 校验令牌 | - 客户端负责发送令牌,无需校验。 | - 服务端调用 oauth2Valid 校验令牌: |
- 提取 authorization 元数据字段; | ||
- 解析 Bearer 令牌; | ||
- 调用 valid 验证令牌是否与合法令牌匹配。 | ||
6. 处理请求 | - 流式 RPC 请求携带认证凭据后,交由具体的业务逻辑处理。 | - 如果校验通过,调用 handler 继续处理业务逻辑;如果校验失败,返回错误拒绝请求。 |
逻辑说明
-
客户端:
- 通过 OAuth2 获取令牌,并将其封装成
credentials.PerRPCCredentials
。 - 流式拦截器负责在每次请求中自动附加
Authorization
元数据,携带格式为Bearer <token>
的令牌。 - 认证元数据通过拦截器透明处理,业务代码无需感知。
- 通过 OAuth2 获取令牌,并将其封装成
-
服务端:
- 流式拦截器负责拦截每个流式请求。
- 从流上下文提取
Authorization
元数据字段,解析令牌并校验合法性。 - 校验通过
客户端需要在每次请求中附加令牌,可以通过拦截器实现。
package client
import (
"context"
"fmt"
"google.golang.org/grpc"
)
// 客户端需要在每次请求中附加令牌,可以通过拦截器实现。
// 两种拦截器的主要目的是确保每个请求(无论是一元请求还是流式请求)都附带上认证令牌(Token)
/*
在附加拦截器的主要作用是在每个单独的请求中附加 OAuth2 令牌。
1.检查是否已经配置
2.没有配置的话调用配置
3.invoke调用实际请求
*/
// UnaryInterceptor 一元请求拦截器
func UnaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
fmt.Println("client UnaryInterceptor")
var credsConfigured bool
for _, opt := range opts {
_, ok := opt.(*grpc.PerRPCCredsCallOption) // 检查是否已经有 PerRPCCredentials 被配置
if ok { // 检查令牌是否已经配置
credsConfigured = true
break
}
}
if !credsConfigured {
opts = append(opts, grpc.PerRPCCredentials(GetPerRPCCredentials(FetchToken()))) // 调用 FetchToken() 获取 OAuth2 令牌
}
return invoker(ctx, method, req, reply, cc, opts...)
}
// StreamInterceptor 流式拦截器
func StreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
fmt.Println("client StreamInterceptor")
var credsConfigured bool
for _, opt := range opts {
_, ok := opt.(*grpc.PerRPCCredsCallOption)
if ok {
credsConfigured = true
break
}
}
if !credsConfigured {
opts = append(opts, grpc.PerRPCCredentials(GetPerRPCCredentials(FetchToken())))
}
return streamer(ctx, desc, cc, method, opts...)
}
客户端的token附加逻辑
package client
import (
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/oauth"
)
/*
1.获取令牌 FetchToken
2.创建认证凭据 使用GetPerRPCCredentials,将string封装为PerRPCCredentials 用于gRPC身份认证
3.配置连接选项 GetAuth,将认证凭证添加到gRPC连接配置中
4.建立连接发送请求,grpc.Dial传入GetAuth认证选项
*/
// GetAuth
// 提供一个 gRPC 的 DialOption,用于在每个请求中自动附加 OAuth2 认证令牌。
// 通过调用 GetPerRPCCredentials,将 token 转换为 gRPC 可用的凭据。
func GetAuth(token string) grpc.DialOption {
perRPC := GetPerRPCCredentials(token)
return grpc.WithPerRPCCredentials(perRPC) // 创建一个 grpc.DialOption,可在建立 gRPC 连接时使用
}
// GetPerRPCCredentials
// 将静态令牌(token)封装为 gRPC 的 PerRPCCredentials 对象,这个对象会在每个请求中附带到元数据中。
func GetPerRPCCredentials(token string) credentials.PerRPCCredentials {
// 使用 oauth2.StaticTokenSource 创建一个静态令牌源。
//将静态令牌源封装为 gRPC 的 oauth.Token。
//返回的对象会在每个 gRPC 请求中附加到元数据中,作为认证凭据。
return oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})} // oauth2依赖下载:go get golang.org/x/oauth2
}
// FetchToken 模拟一个获取 OAuth2 令牌的方法,实际场景中应该从身份认证服务获取动态令牌
func FetchToken() string {
return "some-secret-token"
}
服务端通过拦截器验证每个请求是否包含合法的令牌。
package server
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"strings"
)
// 服务端通过拦截器验证每个请求是否包含合法的令牌。
// 两种拦截器的主要目的是确保每个请求(无论是一元请求还是流式请求)都附带上认证令牌(Token)
// server端作用:一个 gRPC 服务端的认证机制,通过一元请求拦截器和流式拦截器校验客户端请求中是否包含合法的身份认证令牌。
/*
1.客户端发起gRPC请求,在元数据中附加Authorization字段
2.服务端拦截器拦截
3.校验令牌
4.认证结果
*/
// UnaryInterceptor 一元请求拦截器
func UnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
fmt.Println("server UnaryInterceptor")
fmt.Println(info)
err = oauth2Valid(ctx)
if err != nil {
return nil, err
}
return handler(ctx, req)
}
// StreamInterceptor 流式拦截器
func StreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
fmt.Println("server StreamInterceptor")
fmt.Println(info)
err := oauth2Valid(ss.Context())
if err != nil {
return err
}
return handler(srv, ss)
}
func oauth2Valid(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return errors.New("元数据获取失败,身份认证失败")
}
authorization := md["authorization"]
if !valid(authorization) {
return errors.New("身份令牌校验失败,身份认证失败")
}
return nil
}
// 令牌验证逻辑
func valid(authorization []string) bool {
if len(authorization) < 1 {
return false
}
// 提取并校验 Bearer 令牌
token := strings.TrimPrefix(authorization[0], "Bearer ")
return token == fetchToken()
}
// 获取合法令牌
// 这是一个静态实现,实际应用中应从数据库或配置文件动态加载。
func fetchToken() string {
return "some-secret-token"
}
客户端逻辑与流式拦截器实现
- 客户端通过 FetchToken 函数获取 OAuth2 令牌。
- 使用 GetPerRPCCredentials 函数将令牌封装为 grpc.PerRPCCredentials。
- 客户端流式拦截器 StreamInterceptor 判断是否附加了凭据,如果没有则添加。
- 配置流式拦截器,所有请求自动附带 Authorization 元数据(格式:Bearer )。
- 流式 RPC 请求携带认证信息后,正常发往服务端。
服务端逻辑与流式拦截器实现
- 服务端通过静态 fetchToken() 或动态服务获取合法的认证令牌。
- 服务端流式拦截器 StreamInterceptor 提取流上下文中的 Metadata。
- 调用 oauth2Valid 校验 Authorization 元数据:
- 提取 Authorization 字段