mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-12 12:43:32 +08:00
286 lines
7.2 KiB
Markdown
286 lines
7.2 KiB
Markdown
---
|
||
name: laravel-security
|
||
description: Laravel 安全最佳实践,涵盖认证/授权、验证、CSRF、批量赋值、文件上传、密钥管理、速率限制和安全部署。
|
||
origin: ECC
|
||
---
|
||
|
||
# Laravel 安全最佳实践
|
||
|
||
针对 Laravel 应用程序的全面安全指导,以防范常见漏洞。
|
||
|
||
## 何时启用
|
||
|
||
* 添加身份验证或授权时
|
||
* 处理用户输入和文件上传时
|
||
* 构建新的 API 端点时
|
||
* 管理密钥和环境设置时
|
||
* 强化生产环境部署时
|
||
|
||
## 工作原理
|
||
|
||
* 中间件提供基础保护(通过 `VerifyCsrfToken` 实现 CSRF,通过 `SecurityHeaders` 实现安全标头)。
|
||
* 守卫和策略强制执行访问控制(`auth:sanctum`、`$this->authorize`、策略中间件)。
|
||
* 表单请求在输入到达服务之前进行验证和整形(`UploadInvoiceRequest`)。
|
||
* 速率限制在身份验证控制之外增加滥用保护(`RateLimiter::for('login')`)。
|
||
* 数据安全来自加密转换、批量赋值保护以及签名路由(`URL::temporarySignedRoute` + `signed` 中间件)。
|
||
|
||
## 核心安全设置
|
||
|
||
* 生产环境中设置 `APP_DEBUG=false`
|
||
* `APP_KEY` 必须设置,并在泄露时轮换
|
||
* 设置 `SESSION_SECURE_COOKIE=true` 和 `SESSION_SAME_SITE=lax`(对于敏感应用,使用 `strict`)
|
||
* 配置受信任的代理以正确检测 HTTPS
|
||
|
||
## 会话和 Cookie 强化
|
||
|
||
* 设置 `SESSION_HTTP_ONLY=true` 以防止 JavaScript 访问
|
||
* 对高风险流程使用 `SESSION_SAME_SITE=strict`
|
||
* 在登录和权限变更时重新生成会话
|
||
|
||
## 身份验证与令牌
|
||
|
||
* 使用 Laravel Sanctum 或 Passport 进行 API 身份验证
|
||
* 对于敏感数据,优先使用带有刷新流程的短期令牌
|
||
* 在注销和账户泄露时撤销令牌
|
||
|
||
路由保护示例:
|
||
|
||
```php
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Route;
|
||
|
||
Route::middleware('auth:sanctum')->get('/me', function (Request $request) {
|
||
return $request->user();
|
||
});
|
||
```
|
||
|
||
## 密码安全
|
||
|
||
* 使用 `Hash::make()` 哈希密码,切勿存储明文
|
||
* 使用 Laravel 的密码代理进行重置流程
|
||
|
||
```php
|
||
use Illuminate\Support\Facades\Hash;
|
||
use Illuminate\Validation\Rules\Password;
|
||
|
||
$validated = $request->validate([
|
||
'password' => ['required', 'string', Password::min(12)->letters()->mixedCase()->numbers()->symbols()],
|
||
]);
|
||
|
||
$user->update(['password' => Hash::make($validated['password'])]);
|
||
```
|
||
|
||
## 授权:策略与门面
|
||
|
||
* 使用策略进行模型级授权
|
||
* 在控制器和服务中强制执行授权
|
||
|
||
```php
|
||
$this->authorize('update', $project);
|
||
```
|
||
|
||
使用策略中间件进行路由级强制执行:
|
||
|
||
```php
|
||
use Illuminate\Support\Facades\Route;
|
||
|
||
Route::put('/projects/{project}', [ProjectController::class, 'update'])
|
||
->middleware(['auth:sanctum', 'can:update,project']);
|
||
```
|
||
|
||
## 验证与数据清理
|
||
|
||
* 始终使用表单请求验证输入
|
||
* 使用严格的验证规则和类型检查
|
||
* 切勿信任请求负载中的派生字段
|
||
|
||
## 批量赋值保护
|
||
|
||
* 使用 `$fillable` 或 `$guarded`,避免使用 `Model::unguard()`
|
||
* 优先使用 DTO 或显式的属性映射
|
||
|
||
## SQL 注入防范
|
||
|
||
* 使用 Eloquent 或查询构建器的参数绑定
|
||
* 除非绝对必要,避免使用原生 SQL
|
||
|
||
```php
|
||
DB::select('select * from users where email = ?', [$email]);
|
||
```
|
||
|
||
## XSS 防范
|
||
|
||
* Blade 默认转义输出(`{{ }}`)
|
||
* 仅对可信的、已清理的 HTML 使用 `{!! !!}`
|
||
* 使用专用库清理富文本
|
||
|
||
## CSRF 保护
|
||
|
||
* 保持 `VerifyCsrfToken` 中间件启用
|
||
* 在表单中包含 `@csrf`,并为 SPA 请求发送 XSRF 令牌
|
||
|
||
对于使用 Sanctum 的 SPA 身份验证,确保配置了有状态请求:
|
||
|
||
```php
|
||
// config/sanctum.php
|
||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
|
||
```
|
||
|
||
## 文件上传安全
|
||
|
||
* 验证文件大小、MIME 类型和扩展名
|
||
* 尽可能将上传文件存储在公开路径之外
|
||
* 如果需要,扫描文件以查找恶意软件
|
||
|
||
```php
|
||
final class UploadInvoiceRequest extends FormRequest
|
||
{
|
||
public function authorize(): bool
|
||
{
|
||
return (bool) $this->user()?->can('upload-invoice');
|
||
}
|
||
|
||
public function rules(): array
|
||
{
|
||
return [
|
||
'invoice' => ['required', 'file', 'mimes:pdf', 'max:5120'],
|
||
];
|
||
}
|
||
}
|
||
```
|
||
|
||
```php
|
||
$path = $request->file('invoice')->store(
|
||
'invoices',
|
||
config('filesystems.private_disk', 'local') // set this to a non-public disk
|
||
);
|
||
```
|
||
|
||
## 速率限制
|
||
|
||
* 在身份验证和写入端点应用 `throttle` 中间件
|
||
* 对登录、密码重置和 OTP 使用更严格的限制
|
||
|
||
```php
|
||
use Illuminate\Cache\RateLimiting\Limit;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\RateLimiter;
|
||
|
||
RateLimiter::for('login', function (Request $request) {
|
||
return [
|
||
Limit::perMinute(5)->by($request->ip()),
|
||
Limit::perMinute(5)->by(strtolower((string) $request->input('email'))),
|
||
];
|
||
});
|
||
```
|
||
|
||
## 密钥与凭据
|
||
|
||
* 切勿将密钥提交到源代码管理
|
||
* 使用环境变量和密钥管理器
|
||
* 密钥暴露后及时轮换,并使会话失效
|
||
|
||
## 加密属性
|
||
|
||
对静态的敏感列使用加密转换。
|
||
|
||
```php
|
||
protected $casts = [
|
||
'api_token' => 'encrypted',
|
||
];
|
||
```
|
||
|
||
## 安全标头
|
||
|
||
* 在适当的地方添加 CSP、HSTS 和框架保护
|
||
* 使用受信任的代理配置来强制执行 HTTPS 重定向
|
||
|
||
设置标头的中间件示例:
|
||
|
||
```php
|
||
use Illuminate\Http\Request;
|
||
use Symfony\Component\HttpFoundation\Response;
|
||
|
||
final class SecurityHeaders
|
||
{
|
||
public function handle(Request $request, \Closure $next): Response
|
||
{
|
||
$response = $next($request);
|
||
|
||
$response->headers->add([
|
||
'Content-Security-Policy' => "default-src 'self'",
|
||
'Strict-Transport-Security' => 'max-age=31536000', // add includeSubDomains/preload only when all subdomains are HTTPS
|
||
'X-Frame-Options' => 'DENY',
|
||
'X-Content-Type-Options' => 'nosniff',
|
||
'Referrer-Policy' => 'no-referrer',
|
||
]);
|
||
|
||
return $response;
|
||
}
|
||
}
|
||
```
|
||
|
||
## CORS 与 API 暴露
|
||
|
||
* 在 `config/cors.php` 中限制来源
|
||
* 对于经过身份验证的路由,避免使用通配符来源
|
||
|
||
```php
|
||
// config/cors.php
|
||
return [
|
||
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
||
'allowed_origins' => ['https://app.example.com'],
|
||
'allowed_headers' => [
|
||
'Content-Type',
|
||
'Authorization',
|
||
'X-Requested-With',
|
||
'X-XSRF-TOKEN',
|
||
'X-CSRF-TOKEN',
|
||
],
|
||
'supports_credentials' => true,
|
||
];
|
||
```
|
||
|
||
## 日志记录与 PII
|
||
|
||
* 切勿记录密码、令牌或完整的卡片数据
|
||
* 在结构化日志中编辑敏感字段
|
||
|
||
```php
|
||
use Illuminate\Support\Facades\Log;
|
||
|
||
Log::info('User updated profile', [
|
||
'user_id' => $user->id,
|
||
'email' => '[REDACTED]',
|
||
'token' => '[REDACTED]',
|
||
]);
|
||
```
|
||
|
||
## 依赖项安全
|
||
|
||
* 定期运行 `composer audit`
|
||
* 谨慎固定依赖项版本,并在出现 CVE 时及时更新
|
||
|
||
## 签名 URL
|
||
|
||
使用签名路由生成临时的、防篡改的链接。
|
||
|
||
```php
|
||
use Illuminate\Support\Facades\URL;
|
||
|
||
$url = URL::temporarySignedRoute(
|
||
'downloads.invoice',
|
||
now()->addMinutes(15),
|
||
['invoice' => $invoice->id]
|
||
);
|
||
```
|
||
|
||
```php
|
||
use Illuminate\Support\Facades\Route;
|
||
|
||
Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
|
||
->name('downloads.invoice')
|
||
->middleware('signed');
|
||
```
|