gin + es 实践 07

用户认证与JWT

本文档介绍Go-ES项目中的用户认证与JWT(JSON Web Token)实现,包括用户注册、登录、令牌刷新以及接口权限控制。

1. 认证系统概述

1.1 认证流程

Go-ES项目采用基于JWT的认证机制,主要流程如下:

  1. 用户注册:用户提供用户名、密码和邮箱进行注册
  2. 用户登录:用户凭借用户名和密码登录,服务器返回访问令牌(Access Token)和刷新令牌(Refresh Token)
  3. 接口认证:客户端在请求头中添加访问令牌,服务器验证令牌有效性
  4. 令牌刷新:访问令牌过期后,客户端使用刷新令牌获取新的令牌对

1.2 JWT令牌设计

采用双令牌系统:

  • 访问令牌(Access Token):短期有效(默认24小时),用于API访问认证
  • 刷新令牌(Refresh Token):长期有效(默认7天),用于获取新的访问令牌

JWT负载包含以下信息:

  • 用户ID(sub)
  • 用户名(username)
  • 用户角色(role)
  • 令牌类型(type)
  • 过期时间(exp)
  • 签发时间(iat)
  • 生效时间(nbf)
  • 签发者(iss)

2. 核心组件

2.1 用户实体

go 复制代码
// User 用户实体
type User struct {
    ID        uint      `json:"id" gorm:"primarykey"`
    Username  string    `json:"username" gorm:"type:varchar(50);uniqueIndex;not null"`
    Password  string    `json:"-" gorm:"type:varchar(100);not null"`
    Email     string    `json:"email" gorm:"type:varchar(100);uniqueIndex"`
    Role      string    `json:"role" gorm:"type:varchar(20);default:'user'"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// SetPassword 设置密码(加密)
func (u *User) SetPassword(password string) error {
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return err
    }
    u.Password = string(hashedPassword)
    return nil
}

// CheckPassword 检查密码是否正确
func (u *User) CheckPassword(password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
    return err == nil
}

2.2 JWT服务

go 复制代码
// Claims JWT声明
type Claims struct {
    UserID   uint      `json:"user_id"`
    Username string    `json:"username"`
    Role     string    `json:"role"`
    Type     TokenType `json:"type"`
    jwt.RegisteredClaims
}

// Service JWT服务
type Service struct {
    config *config.Config
}

// GenerateToken 生成JWT令牌
func (s *Service) GenerateToken(userID uint, username, role string) (string, string, error) {
    // 生成访问令牌和刷新令牌
}

// RefreshToken 刷新访问令牌
func (s *Service) RefreshToken(refreshToken string) (string, string, error) {
    // 验证刷新令牌并生成新的令牌对
}

// ParseToken 解析JWT令牌
func (s *Service) ParseToken(tokenString string) (*Claims, error) {
    // 解析并验证令牌
}

2.3 认证服务接口

go 复制代码
// AuthService 身份验证服务接口
type AuthService interface {
    // Register 注册新用户
    Register(ctx context.Context, username, password, email string) (*entity.User, error)
    
    // Login 用户登录
    Login(ctx context.Context, username, password string) (accessToken string, refreshToken string, user *entity.User, error)
    
    // RefreshToken 刷新令牌
    RefreshToken(ctx context.Context, refreshToken string) (accessToken string, newRefreshToken string, error)
    
    // GetUserByID 通过ID获取用户
    GetUserByID(ctx context.Context, id uint) (*entity.User, error)
    
    // ValidateToken 验证token并返回用户信息
    ValidateToken(ctx context.Context, token string) (*entity.User, error)
}

2.4 认证中间件

go 复制代码
// AuthMiddleware 认证中间件
type AuthMiddleware struct {
    authService service.AuthService
}

// Authenticate 验证用户身份
func (m *AuthMiddleware) Authenticate() gin.HandlerFunc {
    // 从请求头中获取并验证令牌
}

// RequireRole 要求特定角色访问
func (m *AuthMiddleware) RequireRole(role string) gin.HandlerFunc {
    // 验证用户角色权限
}

3. API端点

3.1 用户注册

复制代码
POST /api/v1/auth/register

请求体:

json 复制代码
{
  "username": "johndoe",
  "password": "securepassword",
  "email": "[email protected]"
}

响应:

json 复制代码
{
  "id": 1,
  "username": "johndoe",
  "email": "[email protected]",
  "role": "user"
}

3.2 用户登录

复制代码
POST /api/v1/auth/login

请求体:

json 复制代码
{
  "username": "johndoe",
  "password": "securepassword"
}

响应:

json 复制代码
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "username": "johndoe",
  "email": "[email protected]",
  "role": "user"
}

3.3 刷新令牌

复制代码
POST /api/v1/auth/refresh

请求体:

json 复制代码
{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

响应:

json 复制代码
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

3.4 获取当前用户信息

复制代码
GET /api/v1/user/me

请求头:

复制代码
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

响应:

json 复制代码
{
  "id": 1,
  "username": "johndoe",
  "email": "[email protected]",
  "role": "user"
}

4. 配置说明

JWT相关配置在config.yaml中:

yaml 复制代码
# JWT 配置
jwt:
  secret: "your_secret_key_here"   # JWT密钥,生产环境请使用复杂的随机字符串
  expires_in: 24                   # 访问令牌过期时间(小时)
  refresh_expires_in: 168          # 刷新令牌过期时间(小时,7天)

5. 安全最佳实践

5.1 密码存储

  • 使用bcrypt算法对密码进行单向哈希存储
  • 不在数据库中存储明文密码
  • 使用适当的加密强度(默认使用bcrypt.DefaultCost)

5.2 令牌安全

  • 使用HTTPS传输敏感信息
  • 访问令牌设置较短的过期时间(默认24小时)
  • 刷新令牌每次使用后都会被替换

5.3 API安全

  • 所有敏感接口都使用认证中间件保护
  • 基于角色的访问控制(RBAC)
  • 对输入数据进行严格验证

6. 实现细节

6.1 认证流程详解

  1. 登录流程

    复制代码
    用户登录 -> 验证凭证 -> 生成访问令牌和刷新令牌 -> 返回给客户端
  2. API访问流程

    复制代码
    请求API -> 提取Authorization头中的令牌 -> 验证令牌 -> 提取用户信息 -> 授权
  3. 令牌刷新流程

    复制代码
    访问令牌过期 -> 客户端使用刷新令牌请求新令牌 -> 验证刷新令牌 -> 生成新的令牌对 -> 返回给客户端

6.2 令牌验证细节

  1. 签名验证:验证令牌签名是否有效
  2. 过期检查:验证令牌是否过期
  3. 类型检查:验证令牌类型是否匹配(访问令牌用于API访问,刷新令牌用于刷新)
  4. 用户验证:验证令牌中的用户是否存在

7. 客户端集成指南

7.1 前端存储

  • 将访问令牌存储在内存或sessionStorage中
  • 将刷新令牌安全存储在httpOnly cookie或localStorage中

7.2 请求认证

javascript 复制代码
// 添加认证头
fetch('/api/v1/user/me', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
})

7.3 令牌刷新处理

javascript 复制代码
async function refreshTokens(refreshToken) {
  const response = await fetch('/api/v1/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken })
  });
  return await response.json();
}

// 处理401错误
async function handleApiRequest(url, options) {
  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  if (response.status === 401) {
    // 令牌过期,尝试刷新
    const tokens = await refreshTokens(refreshToken);
    // 更新存储的令牌
    accessToken = tokens.access_token;
    refreshToken = tokens.refresh_token;
    
    // 重试请求
    response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${accessToken}`
      }
    });
  }
  
  return response;
}

8. 测试

8.1 单元测试

go 复制代码
func TestAuthService_Login(t *testing.T) {
    // 模拟用户仓库和JWT服务
    // 测试登录逻辑
}

func TestJWTService_GenerateToken(t *testing.T) {
    // 测试令牌生成
}

func TestJWTService_ParseToken(t *testing.T) {
    // 测试令牌解析
}

8.2 集成测试

go 复制代码
func TestAuthenticationFlow(t *testing.T) {
    // 测试完整的认证流程:注册 -> 登录 -> 访问API -> 刷新令牌
}

9. 扩展与优化

9.1 潜在的扩展

  • 支持OAuth2.0或OpenID Connect集成
  • 添加多因素认证(MFA)
  • 实现单点登录(SSO)

9.2 性能优化

  • 使用缓存存储活跃用户信息
  • 使用Redis存储无效/已撤销的令牌
  • 令牌黑名单机制,提高安全性

9.3 安全增强

  • 实现登录尝试次数限制
  • 添加IP地址和设备指纹验证
  • 实现令牌轮换策略
相关推荐
从零开始学习人工智能1 分钟前
4G与5G网络频率:技术演进与应用场景解析
网络·5g
livemetee7 分钟前
Netty 的 Reactor 模型
网络
努力也学不会java15 分钟前
【HTTP】《HTTP 全原理解析:从请求到响应的奇妙之旅》
java·网络·网络协议·http
久绊A15 分钟前
原生 IP(Native IP)
网络
半路_出家ren23 分钟前
传输层协议 1.TCP 2.UDP
网络·网络安全·udp·wireshark·kali·tcp·gns3
gbase_lmax42 分钟前
gbase8s数据库 tcp连接不同阶段的超时处理
网络·数据库·网络协议·tcp/ip
简婷187019987751 小时前
铁塔基站项目用电能表有哪些?
网络
宏电物联网1 小时前
宏电全新升级单北斗5G电力DTU,为每一公里电力线路注入可靠连接
网络
christine-rr1 小时前
【25软考网工】第五章(8)路由协议RIP、OSPF
运维·网络·网络工程师·软考·考试
SZ1701102312 小时前
数据报(Datagram)与虚电路(Virtual Circuit)的区别
网络·单片机·嵌入式硬件