Files
datahub/docs/AUTH_USAGE.md
2026-02-28 10:38:33 +08:00

371 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JWT 认证系统使用说明
## 概述
本项目已配置完成基于 JWT Token 的用户认证系统,支持 token 刷新功能。
## 技术栈
- **数据库**: PostgreSQL 16
- **认证库**: 96qbhy/hyperf-auth
- **权限管理**: Casbin
- **缓存**: 文件系统缓存(FilesystemCache
## 配置信息
### 环境变量
`.env` 文件中已配置:
```env
# 数据库配置
DB_DRIVER=pgsql
DB_HOST=127.0.0.1
DB_PORT=5416
DB_DATABASE=datahub
DB_USERNAME=datahub
DB_PASSWORD=datahub
# 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=datahub
```
### 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`
**请求参数**:
```json
{
"username": "testuser",
"password": "password123",
"email": "test@example.com"
}
```
**响应示例**:
```json
{
"code": 0,
"message": "注册成功",
"data": {
"id": 1,
"username": "testuser",
"email": "test@example.com"
}
}
```
### 2. 用户登录
**接口**: `POST /api/auth/login`
**请求参数**:
```json
{
"username": "testuser",
"password": "password123"
}
```
**响应示例**:
```json
{
"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`
**请求参数**:
```json
{
"refresh_token": "a1b2c3d4e5f6..."
}
```
**响应示例**:
```json
{
"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...
```
**响应示例**:
```json
{
"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...
```
**响应示例**:
```json
{
"code": 0,
"message": "退出成功"
}
```
## 在代码中使用认证
### 在控制器中获取当前用户
```php
<?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` 中使用中间件:
```php
Router::addGroup('/api/protected', function () {
Router::get('/resource', 'App\Controller\YourController@someAction');
}, [
'middleware' => [App\Middleware\AuthMiddleware::class],
]);
```
## 前端集成示例
### JavaScript/TypeScript
```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`
执行迁移:
```bash
php bin/hyperf.php migrate
```
回滚迁移:
```bash
php bin/hyperf.php migrate:rollback
```
## 缓存配置
### 默认配置(文件系统缓存)
当前使用文件系统缓存,JWT Token 缓存存储在系统临时目录(`sys_get_temp_dir()`)。
**优点**
- 无需额外依赖
- 配置简单
- 适合单机部署
**缺点**
- 不支持分布式部署
- 重启服务器会清空临时目录
### 切换到 Redis(可选)
如需分布式部署或更好的性能,可以切换到 Redis 缓存。
**步骤**
1. 安装 Redis 并配置 `.env` 文件中的连接信息
2. 修改 `config/autoload/auth.php` 中 jwt guard 的缓存配置:
```php
// 注释掉文件系统缓存
// '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 缓存(可选)