Laravel 13 安全特性详解

安全是 Web 应用程序开发的重中之重。Laravel 13 提供了全面的安全特性,帮助开发者构建安全可靠的应用程序。

认证系统

用户认证

1
2
3
4
5
6
7
8
9
10
11
12
use Illuminate\Support\Facades\Auth;

// 尝试登录
if (Auth::attempt(['email' => $email, 'password' => $password])) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}

// 登出
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();

API 认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens;
}

// 创建令牌
$token = $user->createToken('token-name')->plainTextToken;

// 使用令牌
if ($request->user()->tokenCan('read-posts')) {
// 有权限
}

密码确认

1
2
Route::get('/settings', function () {
})->middleware(['auth', 'password.confirm']);

授权系统

Gates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// AppServiceProvider.php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});

Gate::define('delete-post', function (User $user, Post $post) {
return $user->id === $post->user_id || $user->isAdmin();
});

Gate::before(function (User $user, string $ability) {
if ($user->isAdmin()) {
return true;
}
});
}

使用 Gates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Illuminate\Support\Facades\Gate;

if (Gate::allows('update-post', $post)) {
}

if (Gate::denies('update-post', $post)) {
}

Gate::authorize('update-post', $post);

if ($user->can('update-post', $post)) {
}

if ($user->cannot('update-post', $post)) {
}

Policies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
public function viewAny(User $user): bool
{
return true;
}

public function view(User $user, Post $post): bool
{
return $post->isPublished() || $user->id === $post->user_id;
}

public function create(User $user): bool
{
return $user->hasVerifiedEmail();
}

public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}

public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->isAdmin();
}
}

注册 Policies

1
2
3
4
5
6
7
8
9
// AppServiceProvider.php
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
Gate::policy(Post::class, PostPolicy::class);
}

CSRF 保护

启用 CSRF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在表单中
<form method="POST" action="/profile">
@csrf
...
</form>

// 在 AJAX 中
<meta name="csrf-token" content="{{ csrf_token() }}">

$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});

排除路由

1
2
3
4
5
6
7
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'stripe/*',
'http://example.com/foo/bar',
]);
})

XSS 防护

Blade 自动转义

1
2
{{ $userInput }}  {{-- 自动转义 --}
{!! $userInput !!} {{-- 不转义,谨慎使用 --}

手动转义

1
2
3
4
use Illuminate\Support\Str;

$safe = Str::escapeHtml($userInput);
$safe = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

清理 HTML

1
2
3
use Illuminate\Support\Str;

$clean = Str::sanitizeHtml($dirtyHtml);

SQL 注入防护

使用 Eloquent

1
2
3
4
5
// 安全
$users = User::where('email', $email)->get();

// 安全
$users = User::where('email', '=', $email)->get();

使用查询构建器

1
2
3
4
5
6
7
// 安全
$users = DB::table('users')
->where('email', $email)
->get();

// 安全
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);

避免原始查询

1
2
3
4
5
// 危险!不要这样做
$users = DB::select("SELECT * FROM users WHERE email = '{$email}'");

// 安全
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);

密码安全

密码哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Support\Facades\Hash;

// 哈希密码
$hashed = Hash::make($password);

// 验证密码
if (Hash::check($password, $hashedPassword)) {
}

// 检查是否需要重新哈希
if (Hash::needsRehash($hashedPassword)) {
$newHashed = Hash::make($password);
}

密码规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Illuminate\Validation\Rules\Password;

$request->validate([
'password' => [
'required',
'confirmed',
Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(),
],
]);

加密

对称加密

1
2
3
4
5
6
7
8
9
use Illuminate\Support\Facades\Crypt;

// 加密
$encrypted = Crypt::encrypt($value);
$encrypted = Crypt::encryptString($value);

// 解密
$decrypted = Crypt::decrypt($encrypted);
$decrypted = Crypt::decryptString($encrypted);

不加密存储

1
2
3
4
5
6
7
8
9
use Illuminate\Support\Facades\Crypt;

class User extends Model
{
protected $casts = [
'settings' => 'encrypted',
'preferences' => 'encrypted:array',
];
}

会话安全

会话配置

1
2
3
4
5
6
7
8
9
10
// config/session.php
return [
'driver' => env('SESSION_DRIVER', 'database'),
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => true,
'encrypt' => true,
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'strict',
];

会话固定防护

1
2
3
// 登录后重新生成会话
Auth::login($user);
$request->session()->regenerate();

文件上传安全

验证文件

1
2
3
4
5
6
7
8
9
$request->validate([
'avatar' => [
'required',
'file',
'max:5120', // 5MB
'mimes:jpeg,png,gif',
'dimensions:max_width=2000,max_height=2000',
],
]);

安全存储

1
2
3
4
5
6
7
8
9
10
// 使用随机文件名
$path = $request->file('avatar')->store('avatars');

// 验证文件类型
if (! in_array($file->extension(), ['jpg', 'png', 'gif'])) {
throw new \Exception('Invalid file type');
}

// 不要使用用户提供的文件名
$filename = Str::random(40) . '.' . $file->extension();

速率限制

配置速率限制

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email.$request->ip());
});

应用速率限制

1
2
3
4
5
6
7
Route::middleware('throttle:api')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});

Route::middleware('throttle:login')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
});

安全头

添加安全头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);

$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'geolocation=(), microphone=()');

if ($request->isSecure()) {
$response->headers->set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}

return $response;
}
}

Content Security Policy

1
2
3
4
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);

日志和监控

安全日志

1
2
3
4
5
6
7
8
use Illuminate\Support\Facades\Log;

Log::channel('security')->warning('Suspicious activity', [
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'url' => $request->fullUrl(),
'user_id' => $request->user()?->id,
]);

审计日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AuditLog
{
public function log(string $action, array $data): void
{
DB::table('audit_logs')->insert([
'user_id' => auth()->id(),
'action' => $action,
'data' => json_encode($data),
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'created_at' => now(),
]);
}
}

安全最佳实践

1. 保持依赖更新

1
2
composer update
npm audit fix

2. 使用环境变量

1
2
3
4
5
// 好的做法
$apiKey = config('services.stripe.secret');

// 不好的做法
$apiKey = 'sk_live_xxx';

3. 验证所有输入

1
2
3
4
5
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users'],
'role' => ['required', 'in:admin,user,guest'],
]);

4. 最小权限原则

1
2
3
4
// 只授予必要的权限
Gate::define('edit-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});

5. 安全的默认值

1
2
3
4
5
6
7
8
9
// 默认拒绝访问
Gate::define('admin-panel', function (User $user) {
return $user->role === 'admin';
});

// 默认使用安全的配置
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'strict',

总结

Laravel 13 提供了全面的安全特性,包括认证、授权、CSRF 保护、XSS 防护、SQL 注入防护等。通过合理使用这些安全特性,并遵循安全最佳实践,可以构建出安全可靠的 Web 应用程序。记住始终验证用户输入、使用安全的默认配置、保持依赖更新,并对敏感操作进行审计和监控。安全是一个持续的过程,需要不断关注和改进。