You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
1 week ago | |
---|---|---|
.. | ||
README.md | 1 week ago | |
bcrypt.go | 1 week ago | |
bcrypt_test.go | 1 week ago | |
jwt.go | 1 week ago | |
jwt_test.go | 1 week ago |
README.md
JWT 工具使用说明
这是一个基于 Go-Zero 框架的 JWT 工具,支持 token 的生成、验证、刷新和管理功能,并将 token 存储在 Redis 中。
功能特性
- ✅ JWT token 生成和验证
- ✅ Redis 存储管理
- ✅ Token 刷新机制
- ✅ 用户多 token 管理
- ✅ 批量 token 操作
- ✅ 完整的错误处理
- ✅ 性能优化
- ✅ 完整的测试覆盖
依赖说明
# 主要依赖
go get github.com/golang-jwt/jwt/v5
go get github.com/redis/go-redis/v9
# 测试依赖
go get github.com/stretchr/testify
配置说明
1. 配置文件 (usercenter.yaml)
Auth:
AccessSecret: "your-secret-key"
AccessExpire: 604800 # 7天过期时间(秒)
2. 配置结构体 (config.go)
type Auth struct {
AccessSecret string // JWT 密钥
AccessExpire int64 // JWT 过期时间(秒)
}
基本使用方法
1. 初始化 JWT 工具
import (
"backend/utils"
"github.com/redis/go-redis/v9"
)
// 创建 Redis 客户端
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
// 创建 JWT 工具实例
jwtUtil := utils.NewJWTUtil(
"your-secret-key", // 访问密钥
7*24*60*60, // 过期时间(秒)
redisClient, // Redis 客户端
)
2. 生成 Token
ctx := context.Background()
userID := int64(123)
username := "john_doe"
token, err := jwtUtil.GenerateToken(ctx, userID, username)
if err != nil {
log.Printf("生成 token 失败: %v", err)
return
}
fmt.Printf("生成的 token: %s\n", token)
3. 验证 Token
claims, err := jwtUtil.ValidateToken(ctx, token)
if err != nil {
log.Printf("验证 token 失败: %v", err)
return
}
fmt.Printf("用户ID: %d, 用户名: %s\n", claims.UserID, claims.Username)
4. 刷新 Token
newToken, err := jwtUtil.RefreshToken(ctx, oldToken)
if err != nil {
log.Printf("刷新 token 失败: %v", err)
return
}
fmt.Printf("新的 token: %s\n", newToken)
5. 删除 Token (登出)
err := jwtUtil.DeleteToken(ctx, token)
if err != nil {
log.Printf("删除 token 失败: %v", err)
return
}
fmt.Println("登出成功")
高级功能
1. 管理用户多个 Token
// 获取用户所有 token
tokens, err := jwtUtil.GetUserTokens(ctx, userID)
if err != nil {
log.Printf("获取用户 token 失败: %v", err)
return
}
fmt.Printf("用户拥有 %d 个 token\n", len(tokens))
// 删除用户所有 token (强制登出)
err = jwtUtil.DeleteAllUserTokens(ctx, userID)
if err != nil {
log.Printf("删除用户所有 token 失败: %v", err)
return
}
2. 检查 Token 是否存在
exists, err := jwtUtil.TokenExists(ctx, token)
if err != nil {
log.Printf("检查 token 失败: %v", err)
return
}
if exists {
fmt.Println("Token 有效")
} else {
fmt.Println("Token 不存在或已过期")
}
3. 解析 Token (不验证签名)
claims, err := jwtUtil.ParseTokenUnverified(token)
if err != nil {
log.Printf("解析 token 失败: %v", err)
return
}
fmt.Printf("Token 中的用户ID: %d\n", claims.UserID)
在 Go-Zero 服务中集成
1. 更新服务上下文
// internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
JWTUtil *utils.JWTUtil
// ... 其他字段
}
func NewServiceContext(c config.Config) *ServiceContext {
// 创建 Redis 客户端
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})
// 创建 JWT 工具
jwtUtil := utils.NewJWTUtil(
c.Auth.AccessSecret,
c.Auth.AccessExpire,
redisClient,
)
return &ServiceContext{
Config: c,
JWTUtil: jwtUtil,
}
}
2. 在登录逻辑中使用
// internal/logic/loginlogic.go
func (l *LoginLogic) Login(in *pb.LoginRequest) (*pb.LoginResponse, error) {
// 验证用户凭证...
// 生成 JWT token
token, err := l.svcCtx.JWTUtil.GenerateToken(
l.ctx,
user.ID,
user.Username,
)
if err != nil {
return nil, err
}
return &pb.LoginResponse{
Token: token,
User: user,
}, nil
}
3. 在需要认证的逻辑中使用
// internal/logic/getprofilelogic.go
func (l *GetProfileLogic) GetProfile(in *pb.GetProfileRequest) (*pb.GetProfileResponse, error) {
// 验证 token
claims, err := l.svcCtx.JWTUtil.ValidateToken(l.ctx, in.Token)
if err != nil {
return nil, errors.New("无效的 token")
}
// 使用 claims 中的用户信息
userID := claims.UserID
// 获取用户信息...
return &pb.GetProfileResponse{
User: user,
}, nil
}
中间件示例
// JWT 认证中间件
func JWTAuthMiddleware(jwtUtil *utils.JWTUtil) func(next http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 从 Header 中获取 token
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "缺少 token", http.StatusUnauthorized)
return
}
// 去掉 "Bearer " 前缀
if strings.HasPrefix(tokenString, "Bearer ") {
tokenString = tokenString[7:]
}
// 验证 token
claims, err := jwtUtil.ValidateToken(r.Context(), tokenString)
if err != nil {
http.Error(w, "无效的 token", http.StatusUnauthorized)
return
}
// 将用户信息存储到上下文中
ctx := context.WithValue(r.Context(), "userID", claims.UserID)
ctx = context.WithValue(ctx, "username", claims.Username)
// 继续处理请求
next(w, r.WithContext(ctx))
}
}
}
错误处理
JWT 工具返回的错误信息包括:
生成 token 失败
: token 生成过程中的错误存储 token 到 Redis 失败
: Redis 存储错误解析 token 失败
: token 格式错误或签名验证失败无效的 token
: token 无效或已过期token 已过期或不存在
: Redis 中不存在该 tokentoken 中的用户ID与 Redis 中的不匹配
: 数据不一致错误
性能优化建议
- Redis 连接池: 使用 Redis 连接池避免频繁创建连接
- Token 缓存: 对于高频访问的 token,可以考虑内存缓存
- 批量操作: 使用批量 Redis 操作提高性能
- 异步删除: 过期 token 的清理可以异步进行
安全建议
- 密钥管理: 使用强密钥并定期轮换
- HTTPS: 始终在 HTTPS 环境中传输 token
- 过期时间: 设置合理的 token 过期时间
- Redis 安全: 确保 Redis 访问安全
- 日志记录: 记录认证相关的操作日志
测试
运行单元测试:
cd backend/utils
go test -v
运行基准测试:
go test -bench=.
常见问题
Q: 如何处理 token 过期?
A: 使用 RefreshToken
方法刷新 token,或者重新登录获取新的 token。
Q: 如何实现强制登出?
A: 使用 DeleteAllUserTokens
方法删除用户的所有 token。
Q: Redis 连接失败怎么处理?
A: 检查 Redis 服务是否正常运行,并确保连接参数正确。
Q: Token 验证失败的常见原因?
A:
- Token 格式错误
- 签名密钥不匹配
- Token 已过期
- Redis 中不存在该 token
- 网络连接问题
更新日志
v1.0.0
- 基础 JWT 功能实现
- Redis 存储支持
- Token 刷新机制
- 用户多 token 管理
- 完整的测试覆盖