Files
datahub/docs/AUTH_USAGE.md
T

8.0 KiB
Raw Blame History

JWT 认证系统使用说明

概述

本项目已配置完成基于 JWT Token 的用户认证系统,支持 token 刷新功能。

技术栈

  • 数据库: PostgreSQL 16
  • 认证库: 96qbhy/hyperf-auth
  • 权限管理: Casbin
  • 缓存: 文件系统缓存(FilesystemCache

配置信息

环境变量

.env 文件中已配置:

# 数据库配置
DB_DRIVER=pgsql
DB_HOST=127.0.0.1
DB_PORT=5416
DB_DATABASE=dataflow
DB_USERNAME=dataflow
DB_PASSWORD=dataflow

# JWT 配置
SIMPLE_JWT_SECRET=your-secret-key-change-this-in-production  # 生产环境请修改
JWT_HEADER_NAME=Authorization
SIMPLE_JWT_TTL=7200  # Access Token 有效期:2小时
SIMPLE_JWT_REFRESH_TTL=2592000  # Refresh Token 有效期:30天
SIMPLE_JWT_PREFIX=dataflow

Token 设计

  • Access Token:

    • 有效期:2 小时
    • 无状态存储,通过 JWT 签名验证
    • 用于日常 API 请求认证
  • Refresh Token:

    • 有效期:30 天
    • 存储在数据库中,可控制撤销
    • 用于刷新 Access Token

数据表结构

users 表

字段 类型 说明
id bigint 主键
username varchar(100) 用户名(唯一)
password varchar(255) 密码(自动加密)
email varchar(100) 邮箱(唯一)
status tinyint 状态:0=禁用,1=启用
ext text 扩展信息(JSON格式)
refresh_token varchar(500) 刷新令牌
refresh_token_expires_at timestamp 刷新令牌过期时间
created_at timestamp 创建时间
updated_at timestamp 更新时间

API 接口

1. 用户注册

接口: POST /api/auth/register

请求参数:

{
  "username": "testuser",
  "password": "password123",
  "email": "test@example.com"
}

响应示例:

{
  "code": 0,
  "message": "注册成功",
  "data": {
    "id": 1,
    "username": "testuser",
    "email": "test@example.com"
  }
}

2. 用户登录

接口: POST /api/auth/login

请求参数:

{
  "username": "testuser",
  "password": "password123"
}

响应示例:

{
  "code": 0,
  "message": "登录成功",
  "data": {
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
    "refresh_token": "a1b2c3d4e5f6...",
    "token_type": "Bearer",
    "expires_in": 7200,
    "user": {
      "id": 1,
      "username": "testuser",
      "email": "test@example.com"
    }
  }
}

3. 刷新 Token

接口: POST /api/auth/refresh

请求参数:

{
  "refresh_token": "a1b2c3d4e5f6..."
}

响应示例:

{
  "code": 0,
  "message": "Token 刷新成功",
  "data": {
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
    "refresh_token": "new_refresh_token...",
    "token_type": "Bearer",
    "expires_in": 7200
  }
}

4. 获取用户信息(需要认证)

接口: GET /api/user/me

请求头:

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...

响应示例:

{
  "code": 0,
  "message": "获取成功",
  "data": {
    "id": 1,
    "username": "testuser",
    "email": "test@example.com",
    "status": 1,
    "ext": null,
    "created_at": "2025-11-10 10:10:05"
  }
}

5. 退出登录(需要认证)

接口: POST /api/auth/logout

请求头:

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...

响应示例:

{
  "code": 0,
  "message": "退出成功"
}

在代码中使用认证

在控制器中获取当前用户

<?php

namespace App\Controller;

use Qbhy\HyperfAuth\AuthManager;
use Hyperf\HttpServer\Contract\ResponseInterface;

class YourController extends AbstractController
{
    public function someAction(AuthManager $auth, ResponseInterface $response)
    {
        // 获取当前用户
        $user = $auth->guard('jwt')->user();

        if (!$user) {
            return $response->json(['code' => 401, 'message' => '未授权']);
        }

        // 使用用户信息
        $userId = $user->getId();
        $username = $user->username;

        // 你的业务逻辑...
    }
}

保护路由

config/routes.php 中使用中间件:

Router::addGroup('/api/protected', function () {
    Router::get('/resource', 'App\Controller\YourController@someAction');
}, [
    'middleware' => [App\Middleware\AuthMiddleware::class],
]);

前端集成示例

JavaScript/TypeScript

// 登录
async function login(username: string, password: string) {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });

  const data = await response.json();

  // 保存 token
  localStorage.setItem('access_token', data.data.access_token);
  localStorage.setItem('refresh_token', data.data.refresh_token);
}

// 请求 API(自动附带 token
async function fetchAPI(url: string, options: RequestInit = {}) {
  const token = localStorage.getItem('access_token');

  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });

  // 如果 token 过期,自动刷新
  if (response.status === 401) {
    await refreshToken();
    return fetchAPI(url, options); // 重试
  }

  return response.json();
}

// 刷新 token
async function refreshToken() {
  const refreshToken = localStorage.getItem('refresh_token');

  const response = await fetch('/api/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken })
  });

  const data = await response.json();

  localStorage.setItem('access_token', data.data.access_token);
  localStorage.setItem('refresh_token', data.data.refresh_token);
}

数据库迁移

已创建的迁移文件位于 migrations/2025_11_10_021005_create_users_table.php

执行迁移:

php bin/hyperf.php migrate

回滚迁移:

php bin/hyperf.php migrate:rollback

缓存配置

默认配置(文件系统缓存)

当前使用文件系统缓存,JWT Token 缓存存储在系统临时目录(sys_get_temp_dir())。

优点

  • 无需额外依赖
  • 配置简单
  • 适合单机部署

缺点

  • 不支持分布式部署
  • 重启服务器会清空临时目录

切换到 Redis(可选)

如需分布式部署或更好的性能,可以切换到 Redis 缓存。

步骤

  1. 安装 Redis 并配置 .env 文件中的连接信息
  2. 修改 config/autoload/auth.php 中 jwt guard 的缓存配置:
// 注释掉文件系统缓存
// 'cache' => new \Doctrine\Common\Cache\FilesystemCache(sys_get_temp_dir()),

// 启用 Redis 缓存
'cache' => function () {
    return make(\Qbhy\HyperfAuth\HyperfRedisCache::class);
},

安全建议

  1. 修改 JWT Secret: 生产环境务必修改 .env 中的 SIMPLE_JWT_SECRET
  2. HTTPS: 生产环境必须使用 HTTPS,防止 token 被窃取
  3. Token 存储: 前端建议使用 HttpOnly Cookie 存储 token,比 localStorage 更安全
  4. 定期轮换: 建议定期强制用户重新登录,轮换 refresh token
  5. 日志监控: 记录异常的登录尝试和 token 使用

常见问题

Q: Token 过期怎么办?

A: Access Token 过期后,使用 Refresh Token 调用 /api/auth/refresh 接口获取新的 Access Token。

Q: 如何实现记住我功能?

A: Refresh Token 有效期为 30 天,客户端可以在 Access Token 过期时自动使用 Refresh Token 刷新。

Q: 如何强制用户下线?

A: 可以将用户的 status 设置为 0(禁用),或者清空数据库中的 refresh_token 字段。

Q: 支持多设备登录吗?

A: 当前实现是单设备登录(一个用户只有一个 refresh_token)。如需支持多设备,可以创建一个单独的 user_tokens 表。

下一步

  1. 修改 JWT Secret 为安全的随机字符串(生产环境必须)
  2. 根据业务需求扩展 User 模型的 ext 字段
  3. 配置 Casbin 权限规则
  4. 添加更多的业务接口
  5. 如需分布式部署,可配置 Redis 缓存(可选)