Golang实现用户认证与数据存储的最佳实践

Golang实现用户认证与数据存储的最佳实践 大家好,

我是大学三年级学生,这学期一直在做一个用Go编写的小型GRPC应用程序作为独立研究项目。它本质上是一个小型世界模拟,用户可以通过API调用与之互动,并通过GRPC流实时观看。大部分功能已经完成,现在需要攻克的下一个难题是用户身份验证。

目前我需要为大多数API调用(除了观看调用)实现身份验证,以及合适的存储方案(支持最高50MB的文件,每个用户可存储多个文件)。大家对实现身份验证和/或存储有什么建议吗?

以下是我的想法…

我可以尝试使用Firebase…我在Web应用中大量使用过它,它在身份验证和存储方面表现很好,但对于后端重度服务似乎不是最佳方案。我可以让用户在前端通过登录Firebase进行身份验证,但不确定如何将这种验证和令牌传递到服务器。我猜可以在GRPC调用的元数据中发送用户名和密码,然后再次访问Firebase服务器进行验证,但这感觉很不妥当。也可以让他们直接登录并通过我的服务创建令牌,但这样使用Firebase就节省不了多少时间。

另一个考虑是搭建Cassandra数据库,不过听说成本较高。我的项目已经准备好通过kubernetes部署,所以搭建数据库不会太困难,但我担心存储之前提到的较大文件的问题。同时我也担心实现身份验证会耗费大量时间。我一直在研究Auth0,但无法评估其实现难度。

目前我有点迷茫,非常希望能得到建议!我的指导老师在项目规划方面帮助有限 😄


更多关于Golang实现用户认证与数据存储的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

太完美了!我之前完全不知道这个功能的存在,非常感谢。

更多关于Golang实现用户认证与数据存储的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是什么类型的 API?REST?RPC?AMQP 消息?

后端部署在哪里?是否有可用的文件系统?

问题实在太多了……

RPC客户端使用React构建,并通过npm包web-grpc调用后端服务。

目前我正通过Kubernetes部署,在Minikube环境中运行,后续可能迁移至GKE,但尚未完成文件系统配置。我倾向于选择Cassandra数据库作为存储方案。

对于信息不完整的问题深表歉意!

在Go中实现用户认证和数据存储,可以采用以下方案:

用户认证

推荐使用JWT(JSON Web Tokens)结合gRPC拦截器实现认证。以下是一个基础示例:

package main

import (
	"context"
	"fmt"
	"strings"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"

	"github.com/golang-jwt/jwt/v4"
)

type AuthInterceptor struct {
	jwtSecret []byte
}

func (ai *AuthInterceptor) UnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		// 跳过观看调用的认证
		if strings.Contains(info.FullMethod, "Watch") {
			return handler(ctx, req)
		}

		// 从元数据获取令牌
		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "missing metadata")
		}

		authHeaders := md["authorization"]
		if len(authHeaders) == 0 {
			return nil, status.Error(codes.Unauthenticated, "missing authorization header")
		}

		tokenString := strings.TrimPrefix(authHeaders[0], "Bearer ")
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return ai.jwtSecret, nil
		})

		if err != nil || !token.Valid {
			return nil, status.Error(codes.Unauthenticated, "invalid token")
		}

		return handler(ctx, req)
	}
}

// 登录处理示例
func Login(username, password string) (string, error) {
	// 验证用户凭据(这里简化处理)
	if username != "testuser" || password != "testpass" {
		return "", status.Error(codes.Unauthenticated, "invalid credentials")
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"username": username,
		"exp":      jwt.TimeFunc().Add(24 * time.Hour).Unix(),
	})

	return token.SignedString([]byte("your-secret-key"))
}

数据存储

对于50MB以下的文件存储,建议使用本地文件系统或云存储服务。以下是本地存储的示例:

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

type FileStorage struct {
	basePath string
}

func NewFileStorage(basePath string) *FileStorage {
	return &FileStorage{basePath: basePath}
}

func (fs *FileStorage) SaveFile(userID string, fileData []byte, filename string) (string, error) {
	// 创建用户目录
	userDir := filepath.Join(fs.basePath, userID)
	if err := os.MkdirAll(userDir, 0755); err != nil {
		return "", fmt.Errorf("failed to create user directory: %v", err)
	}

	// 生成文件哈希作为唯一标识
	hash := sha256.Sum256(fileData)
	fileHash := hex.EncodeToString(hash[:])
	
	// 保存文件
	filePath := filepath.Join(userDir, fileHash+"_"+filename)
	if err := os.WriteFile(filePath, fileData, 0644); err != nil {
		return "", fmt.Errorf("failed to save file: %v", err)
	}

	return fileHash, nil
}

func (fs *FileStorage) GetFile(userID, fileHash string) ([]byte, error) {
	userDir := filepath.Join(fs.basePath, userID)
	
	// 查找匹配的文件
	files, err := filepath.Glob(filepath.Join(userDir, fileHash+"_*"))
	if err != nil {
		return nil, fmt.Errorf("failed to search files: %v", err)
	}
	
	if len(files) == 0 {
		return nil, fmt.Errorf("file not found")
	}

	return os.ReadFile(files[0])
}

// 使用示例
func main() {
	storage := NewFileStorage("./user_files")
	
	// 保存文件
	fileID, err := storage.SaveFile("user123", []byte("file content"), "document.txt")
	if err != nil {
		fmt.Printf("Error saving file: %v\n", err)
		return
	}
	
	fmt.Printf("File saved with ID: %s\n", fileID)
	
	// 读取文件
	data, err := storage.GetFile("user123", fileID)
	if err != nil {
		fmt.Printf("Error reading file: %v\n", err)
		return
	}
	
	fmt.Printf("File content: %s\n", string(data))
}

gRPC服务集成

在gRPC服务中集成认证和存储:

package main

import (
	"context"

	"google.golang.org/grpc"
)

type WorldSimulationServer struct {
	storage *FileStorage
}

func (s *WorldSimulationServer) SaveUserFile(ctx context.Context, req *SaveFileRequest) (*SaveFileResponse, error) {
	// 认证已在拦截器中处理
	fileID, err := s.storage.SaveFile(req.UserId, req.FileData, req.Filename)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	return &SaveFileResponse{FileId: fileID}, nil
}

func main() {
	storage := NewFileStorage("./user_files")
	interceptor := &AuthInterceptor{jwtSecret: []byte("your-secret-key")}
	
	server := grpc.NewServer(
		grpc.UnaryInterceptor(interceptor.UnaryInterceptor()),
	)
	
	simServer := &WorldSimulationServer{storage: storage}
	RegisterWorldSimulationServer(server, simServer)
	
	// 启动服务器...
}

这个方案提供了完整的认证流程和文件存储功能,避免了外部服务的依赖,可以直接集成到现有的gRPC应用中。JWT认证通过gRPC拦截器自动处理,文件存储使用本地文件系统,支持多用户和多文件存储。

回到顶部