690 lines
17 KiB
Markdown
690 lines
17 KiB
Markdown
# 认证系统开发文档
|
||
|
||
## 概述
|
||
|
||
本文档详细介绍了基于Laravel 12和Laravel Sanctum实现的Token认证系统,适用于Web端、移动端及各种API调用场景。
|
||
|
||
## 系统架构
|
||
|
||
### 1. 整体结构
|
||
|
||
```
|
||
认证系统
|
||
├── 控制器层 (AuthController)
|
||
│ ├── 用户认证逻辑
|
||
│ ├── Token管理
|
||
│ └── 设备管理
|
||
├── 模型层 (User)
|
||
│ ├── 用户数据管理
|
||
│ ├── Sanctum Token集成
|
||
│ └── 数据验证
|
||
├── 路由层 (admin.php)
|
||
│ ├── 公开路由 (登录)
|
||
│ └── 受保护路由 (需Token)
|
||
└── 中间件层 (auth:sanctum)
|
||
├── Token验证
|
||
└── 用户授权
|
||
```
|
||
|
||
### 2. 文件结构
|
||
|
||
```
|
||
app/
|
||
├── Http/Controllers/Admin/
|
||
│ └── AuthController.php # 认证控制器
|
||
├── Models/
|
||
│ └── User.php # 用户模型
|
||
└── Http/Middleware/
|
||
└── (使用Laravel内置auth:sanctum中间件)
|
||
|
||
routes/
|
||
└── admin.php # 后台认证路由
|
||
|
||
database/
|
||
├── migrations/
|
||
│ ├── create_users_table.php # 用户表结构
|
||
│ └── create_personal_access_tokens_table.php # Token表结构
|
||
└── seeders/
|
||
└── AdminUserSeeder.php # 测试用户数据
|
||
|
||
config/
|
||
└── sanctum.php # Sanctum配置文件
|
||
|
||
docs/
|
||
└── 认证系统开发文档.md # 本文档
|
||
```
|
||
|
||
## 核心组件详细说明
|
||
|
||
### 1. AuthController 认证控制器
|
||
|
||
**位置**: `app/Http/Controllers/Admin/AuthController.php`
|
||
|
||
**职责**:
|
||
- 处理用户登录认证
|
||
- 生成和管理API Token
|
||
- 处理用户登出
|
||
- 提供用户信息查询
|
||
- 管理多设备登录
|
||
|
||
**主要方法**:
|
||
|
||
#### login() - 用户登录
|
||
- **功能**: 验证用户凭据,生成Token
|
||
- **输入**: username, password, device_name(可选)
|
||
- **输出**: 用户信息 + Token
|
||
- **验证**: 用户名密码、用户状态检查
|
||
|
||
#### logout() - 登出当前设备
|
||
- **功能**: 删除当前设备的Token
|
||
- **输入**: 当前请求的Token
|
||
- **输出**: 成功确认消息
|
||
|
||
#### logoutAll() - 登出所有设备
|
||
- **功能**: 删除用户所有设备的Token
|
||
- **输入**: 当前请求的Token
|
||
- **输出**: 成功确认消息
|
||
|
||
#### me() - 获取用户信息
|
||
- **功能**: 返回当前登录用户信息
|
||
- **输入**: Token认证
|
||
- **输出**: 用户详细信息 + Token状态
|
||
|
||
#### refresh() - 刷新Token
|
||
- **功能**: 生成新Token,删除旧Token
|
||
- **输入**: 当前Token + device_name
|
||
- **输出**: 新Token信息
|
||
|
||
#### devices() - 获取设备列表
|
||
- **功能**: 查看用户所有登录设备
|
||
- **输入**: Token认证
|
||
- **输出**: 设备列表 + Token信息
|
||
|
||
#### deleteDevice() - 删除指定设备
|
||
- **功能**: 根据Token ID删除指定设备
|
||
- **输入**: token_id参数
|
||
- **输出**: 删除确认消息
|
||
|
||
### 2. User 用户模型
|
||
|
||
**位置**: `app/Models/User.php`
|
||
|
||
**特性**:
|
||
- 继承Laravel Authenticatable
|
||
- 集成HasApiTokens trait (Sanctum)
|
||
- 自定义时间戳字段映射
|
||
- 用户状态验证
|
||
|
||
**重要配置**:
|
||
```php
|
||
// 自定义时间戳字段
|
||
const CREATED_AT = 'create_time';
|
||
const UPDATED_AT = 'update_time';
|
||
|
||
// 可批量赋值字段
|
||
protected $fillable = [
|
||
'username', 'nickname', 'email', 'mobile',
|
||
'password', 'dept_id', 'avatar', 'status'
|
||
];
|
||
|
||
// 隐藏敏感字段
|
||
protected $hidden = ['password'];
|
||
```
|
||
|
||
### 3. 路由配置
|
||
|
||
**位置**: `routes/admin.php`
|
||
|
||
**结构**:
|
||
```php
|
||
// 公开路由 (无需认证)
|
||
Route::prefix('auth')->group(function () {
|
||
Route::post('/login', [AuthController::class, 'login']);
|
||
});
|
||
|
||
// 受保护路由 (需要Token认证)
|
||
Route::middleware('auth:sanctum')->group(function () {
|
||
// 认证相关
|
||
Route::prefix('auth')->group(function () {
|
||
Route::post('/logout', [AuthController::class, 'logout']);
|
||
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
|
||
Route::get('/me', [AuthController::class, 'me']);
|
||
Route::post('/refresh', [AuthController::class, 'refresh']);
|
||
Route::get('/devices', [AuthController::class, 'devices']);
|
||
Route::delete('/devices', [AuthController::class, 'deleteDevice']);
|
||
});
|
||
|
||
// 业务功能
|
||
Route::get('/dashboard', function () { /* 仪表盘逻辑 */ });
|
||
});
|
||
```
|
||
|
||
## 业务流程
|
||
|
||
### 1. 用户登录流程
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[客户端发送登录请求] --> B[AuthController::login]
|
||
B --> C[验证请求参数]
|
||
C --> D{参数是否有效?}
|
||
D -->|否| E[返回参数错误]
|
||
D -->|是| F[验证用户凭据]
|
||
F --> G{用户名密码是否正确?}
|
||
G -->|否| H[返回认证失败]
|
||
G -->|是| I[检查用户状态]
|
||
I --> J{用户是否可用?}
|
||
J -->|否| K[返回用户状态错误]
|
||
J -->|是| L[生成API Token]
|
||
L --> M[返回用户信息和Token]
|
||
```
|
||
|
||
### 2. API请求流程
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[客户端发送API请求] --> B[携带Bearer Token]
|
||
B --> C[auth:sanctum中间件验证]
|
||
C --> D{Token是否有效?}
|
||
D -->|否| E[返回401未授权]
|
||
D -->|是| F[获取Token对应用户]
|
||
F --> G{用户是否存在且可用?}
|
||
G -->|否| H[返回403禁止访问]
|
||
G -->|是| I[执行控制器方法]
|
||
I --> J[返回业务数据]
|
||
```
|
||
|
||
### 3. Token管理流程
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[Token生成] --> B[存储到personal_access_tokens表]
|
||
B --> C[设置设备名称和权限]
|
||
C --> D[Token使用验证]
|
||
D --> E{Token是否过期?}
|
||
E -->|是| F[自动清理]
|
||
E -->|否| G[继续使用]
|
||
G --> H[用户主动登出]
|
||
H --> I[删除指定Token]
|
||
I --> J[Token失效]
|
||
```
|
||
|
||
## 数据库设计
|
||
|
||
### 1. system_users 表 (用户表)
|
||
|
||
```sql
|
||
CREATE TABLE `system_users` (
|
||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||
`username` varchar(50) NOT NULL COMMENT '用户名',
|
||
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
|
||
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
|
||
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
|
||
`password` varchar(255) NOT NULL COMMENT '密码',
|
||
`avatar` varchar(500) DEFAULT NULL COMMENT '头像',
|
||
`dept_id` int DEFAULT NULL COMMENT '部门ID',
|
||
`status` tinyint DEFAULT '1' COMMENT '状态 1:正常 0:禁用',
|
||
`deleted` tinyint DEFAULT '0' COMMENT '删除 1:已删除 0:正常',
|
||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `username` (`username`)
|
||
) COMMENT='系统用户表';
|
||
```
|
||
|
||
### 2. personal_access_tokens 表 (Token表)
|
||
|
||
```sql
|
||
CREATE TABLE `personal_access_tokens` (
|
||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||
`tokenable_type` varchar(255) NOT NULL,
|
||
`tokenable_id` bigint unsigned NOT NULL,
|
||
`name` varchar(255) NOT NULL,
|
||
`token` varchar(64) NOT NULL,
|
||
`abilities` text,
|
||
`last_used_at` timestamp NULL DEFAULT NULL,
|
||
`expires_at` timestamp NULL DEFAULT NULL,
|
||
`created_at` timestamp NULL DEFAULT NULL,
|
||
`updated_at` timestamp NULL DEFAULT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`)
|
||
) COMMENT='API访问令牌表';
|
||
```
|
||
|
||
## 使用方法
|
||
|
||
### 1. 客户端集成示例
|
||
|
||
#### JavaScript/Web端
|
||
```javascript
|
||
class AuthService {
|
||
constructor() {
|
||
this.token = localStorage.getItem('auth_token');
|
||
this.baseURL = '/admin';
|
||
}
|
||
|
||
// 登录
|
||
async login(username, password, deviceName = 'Web Browser') {
|
||
const response = await fetch(`${this.baseURL}/auth/login`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
username,
|
||
password,
|
||
device_name: deviceName
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
this.token = data.data.token.access_token;
|
||
localStorage.setItem('auth_token', this.token);
|
||
return data.data;
|
||
}
|
||
throw new Error(data.message);
|
||
}
|
||
|
||
// 发送认证请求
|
||
async apiRequest(url, options = {}) {
|
||
const headers = {
|
||
'Authorization': `Bearer ${this.token}`,
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
...options.headers
|
||
};
|
||
|
||
return fetch(url, { ...options, headers });
|
||
}
|
||
|
||
// 获取用户信息
|
||
async getUser() {
|
||
const response = await this.apiRequest(`${this.baseURL}/auth/me`);
|
||
return response.json();
|
||
}
|
||
|
||
// 登出
|
||
async logout() {
|
||
await this.apiRequest(`${this.baseURL}/auth/logout`, {
|
||
method: 'POST'
|
||
});
|
||
this.token = null;
|
||
localStorage.removeItem('auth_token');
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 移动端示例 (React Native)
|
||
```javascript
|
||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||
|
||
class MobileAuthService {
|
||
constructor() {
|
||
this.token = null;
|
||
this.baseURL = 'https://your-api.com/admin';
|
||
this.loadToken();
|
||
}
|
||
|
||
async loadToken() {
|
||
this.token = await AsyncStorage.getItem('auth_token');
|
||
}
|
||
|
||
async login(username, password) {
|
||
const deviceInfo = {
|
||
device_name: `${Platform.OS} ${DeviceInfo.getSystemVersion()}`
|
||
};
|
||
|
||
const response = await fetch(`${this.baseURL}/auth/login`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
username,
|
||
password,
|
||
...deviceInfo
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
this.token = data.data.token.access_token;
|
||
await AsyncStorage.setItem('auth_token', this.token);
|
||
return data.data;
|
||
}
|
||
throw new Error(data.message);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 服务端扩展示例
|
||
|
||
#### 添加新的认证路由
|
||
```php
|
||
// routes/admin.php
|
||
Route::middleware('auth:sanctum')->group(function () {
|
||
// 用户管理
|
||
Route::prefix('users')->group(function () {
|
||
Route::get('/', [UserController::class, 'index']);
|
||
Route::post('/', [UserController::class, 'store']);
|
||
Route::put('/{id}', [UserController::class, 'update']);
|
||
Route::delete('/{id}', [UserController::class, 'destroy']);
|
||
});
|
||
});
|
||
```
|
||
|
||
#### 创建新的业务控制器
|
||
```php
|
||
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\BaseController;
|
||
use Illuminate\Http\Request;
|
||
|
||
class UserController extends BaseController
|
||
{
|
||
public function index(Request $request)
|
||
{
|
||
// 获取当前认证用户
|
||
$currentUser = $request->user();
|
||
|
||
// 业务逻辑
|
||
$users = User::paginate(10);
|
||
|
||
return $this->success($users, '获取用户列表成功');
|
||
}
|
||
}
|
||
```
|
||
|
||
## 配置说明
|
||
|
||
### 1. Sanctum配置
|
||
|
||
**文件**: `config/sanctum.php`
|
||
|
||
**重要配置项**:
|
||
```php
|
||
// Token过期时间 (分钟)
|
||
'expiration' => null, // null表示永不过期
|
||
|
||
// 中间件配置
|
||
'middleware' => [
|
||
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
||
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
||
],
|
||
|
||
// 前缀配置
|
||
'prefix' => 'sanctum',
|
||
|
||
// 数据库连接
|
||
'connection' => env('SANCTUM_CONNECTION'),
|
||
```
|
||
|
||
### 2. 环境变量配置
|
||
|
||
**.env 文件**:
|
||
```env
|
||
# 应用配置
|
||
APP_NAME="Study API V2"
|
||
APP_ENV=local
|
||
APP_DEBUG=true
|
||
APP_URL=http://localhost:8000
|
||
|
||
# 数据库配置
|
||
DB_CONNECTION=mysql
|
||
DB_HOST=127.0.0.1
|
||
DB_PORT=3306
|
||
DB_DATABASE=study_api_v2
|
||
DB_USERNAME=root
|
||
DB_PASSWORD=
|
||
|
||
# Sanctum配置
|
||
SANCTUM_STATEFUL_DOMAINS=localhost,127.0.0.1
|
||
SANCTUM_EXPIRATION=null
|
||
```
|
||
|
||
## 安全考虑
|
||
|
||
### 1. Token安全
|
||
|
||
**存储安全**:
|
||
- ✅ 客户端使用安全存储 (Web: localStorage, 移动端: Keychain/Keystore)
|
||
- ✅ 避免在URL参数中传递Token
|
||
- ✅ 使用HTTPS传输
|
||
- ⚠️ 定期轮换Token (refresh机制)
|
||
|
||
**传输安全**:
|
||
- ✅ 始终使用 `Authorization: Bearer {token}` 头
|
||
- ✅ 不在Cookie中存储Token (避免CSRF)
|
||
- ✅ 设置合适的CORS策略
|
||
|
||
### 2. 用户验证
|
||
|
||
**登录安全**:
|
||
- ✅ 密码哈希存储 (bcrypt)
|
||
- ✅ 用户状态检查 (status, deleted字段)
|
||
- ✅ 失败次数限制 (可扩展)
|
||
- ⚠️ 双因素认证 (可扩展)
|
||
|
||
**会话管理**:
|
||
- ✅ 多设备登录管理
|
||
- ✅ 设备标识和追踪
|
||
- ✅ 远程登出功能
|
||
- ✅ Token使用时间记录
|
||
|
||
### 3. API安全
|
||
|
||
**访问控制**:
|
||
- ✅ 基于Token的认证
|
||
- ✅ 路由级别的权限控制
|
||
- ✅ 用户状态实时验证
|
||
- ⚠️ 角色权限系统 (可扩展)
|
||
|
||
**数据保护**:
|
||
- ✅ 敏感数据过滤 (密码等)
|
||
- ✅ 统一错误响应格式
|
||
- ✅ 请求参数验证
|
||
- ✅ SQL注入防护 (Eloquent ORM)
|
||
|
||
## 错误处理
|
||
|
||
### 1. 常见错误码
|
||
|
||
| 错误码 | 说明 | 处理方式 |
|
||
|--------|------|----------|
|
||
| 401 | 未授权访问 | 重新登录 |
|
||
| 403 | 禁止访问 | 检查用户状态 |
|
||
| 422 | 参数验证失败 | 修正请求参数 |
|
||
| 500 | 服务器内部错误 | 联系技术支持 |
|
||
|
||
### 2. 错误响应格式
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "具体错误信息",
|
||
"code": 422,
|
||
"data": null,
|
||
"errors": {
|
||
"username": ["用户名不能为空"],
|
||
"password": ["密码长度至少6位"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 客户端错误处理示例
|
||
|
||
```javascript
|
||
async function handleApiCall() {
|
||
try {
|
||
const response = await authService.apiRequest('/admin/auth/me');
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
switch (response.status) {
|
||
case 401:
|
||
// Token过期或无效,重新登录
|
||
authService.logout();
|
||
window.location.href = '/login';
|
||
break;
|
||
case 403:
|
||
// 用户被禁用
|
||
alert('账户已被禁用,请联系管理员');
|
||
break;
|
||
case 422:
|
||
// 参数错误
|
||
console.error('参数错误:', data.errors);
|
||
break;
|
||
default:
|
||
console.error('API错误:', data.message);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 处理成功响应
|
||
console.log('用户信息:', data.data);
|
||
} catch (error) {
|
||
console.error('网络错误:', error);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 性能优化
|
||
|
||
### 1. 数据库优化
|
||
|
||
**索引优化**:
|
||
- ✅ username字段唯一索引
|
||
- ✅ personal_access_tokens表Token字段索引
|
||
- ✅ 用户状态字段索引
|
||
|
||
**查询优化**:
|
||
- ✅ 避免N+1查询问题
|
||
- ✅ 使用Eloquent关联查询
|
||
- ✅ 合理使用缓存
|
||
|
||
### 2. Token管理优化
|
||
|
||
**清理策略**:
|
||
- ✅ 定期清理过期Token
|
||
- ✅ 限制单用户Token数量
|
||
- ✅ 异步处理Token操作
|
||
|
||
**缓存策略**:
|
||
- ⚠️ Redis缓存用户信息 (可扩展)
|
||
- ⚠️ Token黑名单缓存 (可扩展)
|
||
|
||
## 监控和日志
|
||
|
||
### 1. 日志记录
|
||
|
||
**登录日志**:
|
||
```php
|
||
// 在AuthController中添加
|
||
Log::info('User login attempt', [
|
||
'username' => $request->username,
|
||
'ip' => $request->ip(),
|
||
'user_agent' => $request->userAgent(),
|
||
'timestamp' => now()
|
||
]);
|
||
```
|
||
|
||
**API访问日志**:
|
||
```php
|
||
// 可以创建中间件记录API访问
|
||
Log::info('API access', [
|
||
'user_id' => Auth::id(),
|
||
'endpoint' => $request->path(),
|
||
'method' => $request->method(),
|
||
'ip' => $request->ip()
|
||
]);
|
||
```
|
||
|
||
### 2. 监控指标
|
||
|
||
**关键指标**:
|
||
- 登录成功/失败率
|
||
- Token使用频率
|
||
- API响应时间
|
||
- 并发用户数
|
||
|
||
## 注意事项
|
||
|
||
### 1. 开发注意事项
|
||
|
||
**代码规范**:
|
||
- ✅ 遵循PSR-4自动加载规范
|
||
- ✅ 使用Laravel最佳实践
|
||
- ✅ 保持代码注释完整
|
||
- ✅ 单一职责原则
|
||
|
||
**测试要求**:
|
||
- ⚠️ 编写单元测试
|
||
- ⚠️ 集成测试覆盖
|
||
- ⚠️ API文档同步更新
|
||
|
||
### 2. 部署注意事项
|
||
|
||
**环境配置**:
|
||
- ✅ 生产环境关闭DEBUG
|
||
- ✅ 配置正确的APP_URL
|
||
- ✅ 设置强密码和密钥
|
||
- ✅ 配置HTTPS
|
||
|
||
**安全配置**:
|
||
- ✅ 限制文件权限
|
||
- ✅ 配置防火墙规则
|
||
- ✅ 定期更新依赖包
|
||
- ✅ 监控异常访问
|
||
|
||
### 3. 维护注意事项
|
||
|
||
**定期维护**:
|
||
- 清理过期Token
|
||
- 检查用户状态
|
||
- 更新安全补丁
|
||
- 备份重要数据
|
||
|
||
**扩展考虑**:
|
||
- 角色权限系统
|
||
- 多租户支持
|
||
- 接口版本控制
|
||
- 限流和熔断
|
||
|
||
## 技术支持
|
||
|
||
### 1. 常见问题
|
||
|
||
**Q: Token过期时间如何设置?**
|
||
A: 在 `config/sanctum.php` 中设置 `expiration` 字段,单位为分钟。设置为 `null` 表示永不过期。
|
||
|
||
**Q: 如何实现Token自动刷新?**
|
||
A: 客户端可以调用 `/admin/auth/refresh` 接口获取新Token,同时删除旧Token。
|
||
|
||
**Q: 如何限制用户同时登录设备数?**
|
||
A: 在登录时可以检查用户现有Token数量,超过限制时删除最旧的Token。
|
||
|
||
### 2. 扩展资源
|
||
|
||
**相关文档**:
|
||
- [Laravel Sanctum官方文档](https://laravel.com/docs/11.x/sanctum)
|
||
- [Laravel认证文档](https://laravel.com/docs/11.x/authentication)
|
||
- [API资源文档](https://laravel.com/docs/11.x/eloquent-resources)
|
||
|
||
**工具推荐**:
|
||
- Postman/Insomnia (API测试)
|
||
- Laravel Telescope (调试工具)
|
||
- Laravel Horizon (队列监控)
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2024年12月
|
||
**作者**: 开发团队
|
||
**审核**: 技术负责人
|