17 KiB
17 KiB
认证系统开发文档
概述
本文档详细介绍了基于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)
- 自定义时间戳字段映射
- 用户状态验证
重要配置:
// 自定义时间戳字段
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
结构:
// 公开路由 (无需认证)
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. 用户登录流程
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请求流程
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管理流程
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 表 (用户表)
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表)
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端
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)
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. 服务端扩展示例
添加新的认证路由
// 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
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
重要配置项:
// 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 文件:
# 应用配置
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. 错误响应格式
{
"success": false,
"message": "具体错误信息",
"code": 422,
"data": null,
"errors": {
"username": ["用户名不能为空"],
"password": ["密码长度至少6位"]
}
}
3. 客户端错误处理示例
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. 日志记录
登录日志:
// 在AuthController中添加
Log::info('User login attempt', [
'username' => $request->username,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'timestamp' => now()
]);
API访问日志:
// 可以创建中间件记录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. 扩展资源
相关文档:
工具推荐:
- Postman/Insomnia (API测试)
- Laravel Telescope (调试工具)
- Laravel Horizon (队列监控)
文档版本: v1.0
最后更新: 2024年12月
作者: 开发团队
审核: 技术负责人