Scott

如何在 Kratos 框架中集成 JWT(JSON Web Tokens)进行身份验证和授权 3 months ago

5177个字符
共有23人围观

Kratos 提供了灵活的方式来集成 JWT,通常会涉及到以下几个关键步骤和组件:

1. 引入 JWT 库:

您需要在您的 Kratos 项目中引入一个 Go 语言的 JWT 库。比较流行的选择包括:

  • github.com/golang-jwt/jwt/v5: 官方推荐的 JWT 库,功能完善且维护良好。
  • github.com/dgrijalva/jwt-go (已归档): 一个较早的库,但现在已经归档,不建议在新项目中使用。

您可以使用 go get 命令来安装您选择的库。例如,使用官方推荐的库:

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

2. 配置 JWT:

您需要配置 JWT 的相关参数,例如:

  • 密钥 (Secret Key): 用于签名和验证 JWT 的密钥,务必妥善保管。
  • 过期时间 (Expiration Time): JWT 的有效时长。
  • 签发者 (Issuer): 标识 JWT 的签发方。
  • 受众 (Audience): 标识 JWT 的接收方(可选)。
  • 令牌头 (Token Header) 的算法 (Algorithm): 常用的有 HS256

这些配置通常放在您的 Kratos 配置文件(例如 config/config.yaml 或通过环境变量)中。

jwt:
  secret: "your-secret-key"
  expire: 3600  # 单位:秒
  issuer: "my-app"

3. 生成 JWT:

在用户登录成功后,您需要生成 JWT 并返回给客户端。这通常在您的认证服务 (Auth Service) 中完成。

import (
	"time"

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

// 定义 JWT 中需要包含的 Claims
type Claims struct {
	UserID int64 `json:"user_id"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}

// 生成 JWT
func GenerateToken(userID int64, username string, secretKey string, expireDuration time.Duration, issuer string) (string, error) {
	now := time.Now()
	claims := Claims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(now.Add(expireDuration)),
			IssuedAt:  jwt.NewNumericDate(now),
			NotBefore: jwt.NewNumericDate(now),
			Issuer:    issuer,
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signedToken, err := token.SignedString([]byte(secretKey))
	if err != nil {
		return "", err
	}
	return signedToken, nil
}

// 在您的登录处理逻辑中调用 GenerateToken
// 例如:
// token, err := GenerateToken(user.ID, user.Username, cfg.JWT.Secret, time.Duration(cfg.JWT.Expire)*time.Second, cfg.JWT.Issuer)
// if err != nil {
// 	// 处理错误
// }
// 返回 token 给客户端

4. 验证 JWT:

在需要进行身份验证的 API 接口中,您需要验证客户端请求中携带的 JWT。这通常通过一个 中间件 (Middleware) 来实现。

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/golang-jwt/jwt/v5"
)

// 自定义一个从上下文中获取用户信息的 Key
type UserContextKey struct{}

// JWT 验证中间件
func JWTAuth(secretKey string) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
			if tr, ok := transport.FromServerContext(ctx); ok {
				authHeader := tr.RequestHeader().Get("Authorization")
				if authHeader == "" {
					return nil, fmt.Errorf("missing authorization header")
				}

				parts := strings.SplitN(authHeader, " ", 2)
				if !(len(parts) == 2 && parts[0] == "Bearer") {
					return nil, fmt.Errorf("invalid authorization header format")
				}

				tokenString := parts[1]

				claims := &Claims{}
				token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
					if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
						return nil, fmt.Errorf("invalid signing method")
					}
					return []byte(secretKey), nil
				})

				if err != nil {
					return nil, fmt.Errorf("invalid token: %w", err)
				}

				if claims, ok := token.Claims.(*Claims); ok && token.Valid {
					// 将用户信息存储到上下文中,供后续处理使用
					newCtx := context.WithValue(ctx, UserContextKey{}, claims)
					return handler(newCtx, req)
				}
			}
			return nil, fmt.Errorf("not a http request")
		}
	}
}

// 在您的 Service 或 Endpoint 中使用 JWTAuth 中间件
// 例如,在 gRPC 服务中:
// grpcServer := grpc.NewServer(
// 	grpc.Middleware(
// 		recovery.Recovery(),
// 		logging.Server(),
// 		jwtAuth.JWTAuth(cfg.JWT.Secret), // 应用 JWT 验证中间件
// 	),
// )

// 在 HTTP 服务中:
// httpServer := http.NewServer(
// 	http.Middleware(
// 		recovery.Recovery(),
// 		logging.Server(),
// 		jwtAuth.JWTAuth(cfg.JWT.Secret), // 应用 JWT 验证中间件
// 	),
// )

// 在您的 Handler 中获取用户信息
// func (s *YourService) YourMethod(ctx context.Context, req *YourRequest) (*YourResponse, error) {
// 	if claims, ok := ctx.Value(jwtAuth.UserContextKey{}).(*jwtAuth.Claims); ok {
// 		userID := claims.UserID
// 		username := claims.Username
// 		// 使用用户信息进行业务逻辑处理
// 		fmt.Printf("User ID: %d, Username: %s\n", userID, username)
// 	}
// 	// ...
// 	return &YourResponse{}, nil
// }

5. 在客户端使用 JWT:

客户端在登录成功后会收到 JWT,通常会将 JWT 存储在本地(例如 localStoragesessionStorage 或 Cookie 中)。在后续的 API 请求中,客户端需要将 JWT 放在请求头中,通常使用 Authorization 头,并加上 Bearer 前缀。

Authorization: Bearer <your_jwt_token>

总结关键步骤:

  1. 引入 JWT 库。
  2. 配置 JWT 相关参数(密钥、过期时间等)。
  3. 在用户认证成功后,使用密钥和配置生成 JWT。
  4. 创建一个中间件来验证请求中的 JWT。
  5. 在需要身份验证的路由上应用该中间件。
  6. (可选)将解析后的用户信息存储到上下文中,方便后续处理。
  7. 客户端在请求头中携带 JWT。

最佳实践和注意事项:

  • 保护好您的密钥: 密钥是 JWT 安全的关键,务必使用强密钥并妥善保管,避免泄露。
  • 设置合理的过期时间: 过短的过期时间会频繁要求用户重新登录,过长的过期时间会增加安全风险。
  • 使用 HTTPS: 确保您的应用程序通过 HTTPS 提供服务,以防止 JWT 在传输过程中被窃取。
  • 考虑使用刷新令牌 (Refresh Token): 对于需要长时间保持用户登录状态的应用,可以考虑使用刷新令牌机制来在 Access Token 过期后安全地获取新的 Access Token,而无需用户重新登录。
  • 了解 JWT 的 Claims: 在 JWT 的 Payload (Claims) 中只存储必要的信息,避免泄露敏感数据。
  • 处理 Token 异常: 在验证 JWT 的过程中,要妥善处理各种可能的错误情况(例如,Token 过期、无效的签名等)。

希望这些信息能够帮助您在 Kratos 中成功集成 JWT!如果您有更具体的问题或想了解某个步骤的更详细实现,请随时告诉我。