Scott

给kratos项目添加jwt auth认证功能 2 months ago

微服务
4892个字符
共有36人围观

为 Kratos 项目添加 JWT 认证功能

下面我将详细介绍如何在 Kratos 项目中集成 JWT 认证功能,包括服务端和客户端的完整实现。

1. 安装所需依赖

首先安装 JWT 相关包:

go get github.com/golang-jwt/jwt/v4

2. 配置 JWT 参数

configs/config.yaml 中添加 JWT 配置:

server:
  http:
    addr: 0.0.0.0:8000
  grpc:
    addr: 0.0.0.0:9000
jwt:
  secret: "your-256-bit-secret"  # 推荐至少32字符
  expires: 720h  # 30天过期

对应的配置文件结构 internal/conf/conf.proto

message Bootstrap {
  Server server = 1;
  Data data = 2;
  Jwt jwt = 3;
}

message Jwt {
  string secret = 1;
  string expires = 2;
}

3. 实现 JWT 工具类

创建 internal/pkg/jwt/jwt.go

package jwt

import (
	"time"
	
	"github.com/golang-jwt/jwt/v4"
)

type Claims struct {
	UserID int64 `json:"user_id"`
	jwt.RegisteredClaims
}

func GenerateToken(secret string, expires time.Duration, userID int64) (string, error) {
	claims := Claims{
		UserID: userID,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(expires)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secret))
}

func ParseToken(secret, tokenString string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(secret), nil
	})
	
	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}
	
	return nil, err
}

4. 实现认证中间件

创建 internal/middleware/auth/auth.go

package auth

import (
	"context"
	"strings"
	
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport/http"
	
	"your_project/pkg/jwt"
)

const (
	authorizationKey = "Authorization"
	bearerWord       = "Bearer"
)

func Auth(jwtSecret string) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			if tr, ok := transport.FromServerContext(ctx); ok {
				if ht, ok := tr.(*http.Transport); ok {
					authHeader := ht.Request().Header.Get(authorizationKey)
					if authHeader == "" {
						return nil, errors.Unauthorized("UNAUTHORIZED", "Authorization header is missing")
					}
					
					token := strings.TrimPrefix(authHeader, bearerWord+" ")
					claims, err := jwt.ParseToken(jwtSecret, token)
					if err != nil {
						return nil, errors.Unauthorized("UNAUTHORIZED", err.Error())
					}
					
					// 将用户ID存入context
					ctx = context.WithValue(ctx, "user_id", claims.UserID)
				}
			}
			return handler(ctx, req)
		}
	}
}

5. 在服务中应用中间件

internal/server/http.go 中:

import (
	"your_project/internal/middleware/auth"
)

func NewHTTPServer(c *conf.Server, jwtConf *conf.Jwt, user *service.UserService) *http.Server {
	opts := []http.ServerOption{
		http.Middleware(
			auth.Auth(jwtConf.Secret), // 添加JWT认证中间件
		),
	}
	
	if c.Http.Network != "" {
		opts = append(opts, http.Network(c.Http.Network))
	}
	if c.Http.Addr != "" {
		opts = append(opts, http.Address(c.Http.Addr))
	}
	if c.Http.Timeout != nil {
		opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
	}
	srv := http.NewServer(opts...)
	
	v1.RegisterUserHTTPServer(srv, user)
	return srv
}

6. 实现登录接口

在用户服务中:

func (s *UserService) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginReply, error) {
	// 验证用户名密码
	user, err := s.repo.VerifyPassword(ctx, req.Username, req.Password)
	if err != nil {
		return nil, err
	}
	
	// 生成JWT
	token, err := jwt.GenerateToken(s.jwt.Secret, s.jwt.Expires, user.ID)
	if err != nil {
		return nil, err
	}
	
	return &pb.LoginReply{
		Token: token,
	}, nil
}

7. 客户端使用 JWT

HTTP 客户端:

func main() {
	conn, err := http.NewClient(
		context.Background(),
		http.WithMiddleware(
			clientMiddleware.JWT("your_token_here"),
		),
		http.WithEndpoint("localhost:8000"),
	)
	// ...
}

gRPC 客户端:

func main() {
	conn, err := grpc.DialInsecure(
		context.Background(),
		grpc.WithEndpoint("localhost:9000"),
		grpc.WithMiddleware(
			grpcMiddleware.JWT("your_token_here"),
		),
	)
	// ...
}

8. 可选 - 排除特定路由

如果需要某些路由不需要认证,可以修改中间件:

func Auth(jwtSecret string, whiteList []string) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (interface{}, error) {
			// 检查是否在白名单
			if tr, ok := transport.FromServerContext(ctx); ok {
				if ht, ok := tr.(*http.Transport); ok {
					for _, path := range whiteList {
						if ht.Request().URL.Path == path {
							return handler(ctx, req)
						}
					}
				}
			}
			
			// 原有认证逻辑...
		}
	}
}

总结

通过以上步骤,你已经为 Kratos 项目添加了完整的 JWT 认证功能:

  1. 配置 JWT 参数
  2. 实现 JWT 生成和解析工具
  3. 创建认证中间件
  4. 在服务中集成中间件
  5. 实现登录接口
  6. 客户端使用 JWT

这种实现方式既保持了 Kratos 的中间件风格,又提供了灵活的认证机制。你可以根据需要调整过期时间、密钥轮换等安全策略。