You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go gRPC服务端基于持久连接的连接时身份认证实现问询

Go gRPC服务端基于持久TCP连接的连接阶段认证方案

核心需求与疑问

需求是在TCP连接建立阶段完成用户身份认证,无需在每次gRPC请求中附带凭证,后续调用GetMyLoginTime时直接返回当前连接用户的登录时间。核心疑问:

  • 在哪验证用户凭证并决定是否允许建立连接?
  • 如何在gRPC方法处理器中关联当前连接的用户数据?

实现思路

通过自定义TCP监听器拦截连接建立过程,在TCP握手完成后、gRPC的HTTP/2握手前验证用户凭证;验证通过后记录用户登录时间,并通过连接的远程地址作为标识,在后续请求中关联用户数据。

具体实现步骤

1. 自定义认证监听器

实现net.Listener接口,在Accept方法中处理凭证验证:

package main

import (
	"errors"
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/status"
	"google.golang.org/grpc/codes"
	test "your/proto/package/path"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)

type AuthListener struct {
	net.Listener
	userStore *sync.Map // key: 远程地址字符串, value: 用户登录时间time.Time
}

// Accept 拦截TCP连接,先验证凭证再交给gRPC服务器
func (l *AuthListener) Accept() (net.Conn, error) {
	conn, err := l.Listener.Accept()
	if err != nil {
		return nil, err
	}

	// 读取客户端发送的凭证(约定格式:username:password\n)
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		conn.Close()
		return nil, err
	}
	credStr := strings.TrimSpace(string(buf[:n]))
	credParts := strings.Split(credStr, ":")
	if len(credParts) != 2 || credParts[0] != "validUser" || credParts[1] != "validPass" {
		conn.Close()
		return nil, errors.New("invalid credentials")
	}

	// 验证通过,记录登录时间
	loginTime := time.Now()
	l.userStore.Store(conn.RemoteAddr().String(), loginTime)

	return conn, nil
}

2. 实现gRPC服务端逻辑

创建gRPC服务器时使用自定义监听器,并实现GetMyLoginTime方法:

type testServer struct {
	test.UnimplementedTestServer
	userStore *sync.Map
}

// GetMyLoginTime 返回当前连接用户的登录时间
func (s *testServer) GetMyLoginTime(ctx context.Context, _ *emptypb.Empty) (*timestamppb.Timestamp, error) {
	// 从请求上下文获取连接的远程地址
	p, ok := peer.FromContext(ctx)
	if !ok {
		return nil, status.Errorf(codes.Unauthenticated, "无法获取连接信息")
	}
	remoteAddr := p.Addr.String()

	// 从存储中读取登录时间
	val, exists := s.userStore.Load(remoteAddr)
	if !exists {
		return nil, status.Errorf(codes.Unauthenticated, "当前连接未完成认证")
	}
	loginTime, ok := val.(time.Time)
	if !ok {
		return nil, status.Errorf(codes.Internal, "用户数据格式错误")
	}

	return timestamppb.New(loginTime), nil
}

func main() {
	userStore := &sync.Map{}
	// 创建基础TCP监听器
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("监听失败: %v", err)
	}
	// 包装为认证监听器
	authLis := &AuthListener{Listener: lis, userStore: userStore}

	// 创建gRPC服务器,可选:监听连接状态变更,清理过期用户数据
	s := grpc.NewServer(grpc.WithConnStateChange(func(conn *grpc.ClientConn, state grpc.ConnState) {
		if state == grpc.Idle || state == grpc.Shutdown {
			userStore.Delete(conn.RemoteAddr().String())
		}
	}))
	test.RegisterTestServer(s, &testServer{userStore: userStore})

	log.Println("gRPC服务启动,监听端口50051")
	if err := s.Serve(authLis); err != nil {
		log.Fatalf("服务启动失败: %v", err)
	}
}

3. 客户端实现(配合认证流程)

客户端需要先发送凭证,再初始化gRPC连接:

package main

import (
	"log"
	"net"

	test "your/proto/package/path"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 先建立TCP连接
	tcpConn, err := net.Dial("tcp", ":50051")
	if err != nil {
		log.Fatalf("TCP连接失败: %v", err)
	}
	defer tcpConn.Close()

	// 发送认证凭证
	_, err = tcpConn.Write([]byte("validUser:validPass\n"))
	if err != nil {
		log.Fatalf("发送凭证失败: %v", err)
	}

	// 将已认证的TCP连接包装为gRPC连接
	grpcConn, err := grpc.NewClient("",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) {
			return tcpConn, nil
		}),
	)
	if err != nil {
		log.Fatalf("创建gRPC客户端失败: %v", err)
	}
	defer grpcConn.Close()

	client := test.NewTestClient(grpcConn)
	resp, err := client.GetMyLoginTime(nil, &emptypb.Empty{})
	if err != nil {
		log.Fatalf("调用GetMyLoginTime失败: %v", err)
	}
	log.Printf("登录时间: %v", resp.AsTime())
}

疑问解答

  1. 如何获取关联的底层TCP连接?
    无需直接操作TCP连接,通过peer.FromContext(ctx)可获取连接的远程地址,以此作为标识关联用户数据;若确实需要原始连接,可在自定义AuthListener的Accept方法中拿到net.Conn实例。

  2. 在哪验证用户凭证并判断连接是否可建立?
    在自定义AuthListener的Accept方法中完成:TCP连接建立后,先读取客户端发送的凭证,验证不通过则直接关闭连接;验证通过后才将连接交给gRPC服务器处理后续的HTTP/2握手和gRPC请求。

内容的提问来源于stack exchange,提问作者xakepp35

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 05:25:21