第七部分:中间件与安全
7.1 认证中间件
// app/Middleware/AuthMiddleware.php
<?php
namespace App\Middleware;
use App\Services\AuthService;
class AuthMiddleware
{
private AuthService $auth;
public function __construct()
{
$this->auth = new AuthService();
}
public function handle(): void
{
// 从请求头获取Token
$headers = getallheaders();
$token = $headers['Authorization'] ?? $_SESSION['api_token'] ?? null;
if (!$token) {
$this->unauthorized('未提供认证Token');
}
$token = str_replace('Bearer ', '', $token);
$user = $this->auth->authenticateByToken($token);
if (!$user) {
$this->unauthorized('Token无效或已过期');
}
if ($user->status != 1) {
$this->unauthorized('账号已被禁用');
}
$this->auth->setUser($user);
}
private function unauthorized(string $message): void
{
http_response_code(401);
header('Content-Type: application/json');
echo json_encode([
'code' => 401,
'message' => $message,
'timestamp' => time()
]);
exit;
}
}
7.2 权限中间件
// app/Middleware/PermissionMiddleware.php
<?php
namespace App\Middleware;
use App\Services\AuthService;
class PermissionMiddleware
{
private AuthService $auth;
public function __construct()
{
$this->auth = new AuthService();
}
public function handle(string $permission = null): void
{
// 从路由中获取需要的权限
// 实际项目中可以从配置或注解中获取
$requiredPermission = $this->getRequiredPermission();
if (!$this->auth->can($requiredPermission)) {
$this->forbidden('无权限访问');
}
}
private function getRequiredPermission(): string
{
// 根据当前请求的URI和方法确定所需权限
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$permissionMap = [
'GET' => [
'#/api/users$#' => 'user.view',
'#/api/users/\d+$#' => 'user.view',
],
'POST' => [
'#/api/users$#' => 'user.create',
],
'PUT' => [
'#/api/users/\d+$#' => 'user.edit',
],
'DELETE' => [
'#/api/users/\d+$#' => 'user.delete',
],
];
foreach ($permissionMap[$method] ?? [] as $pattern => $permission) {
if (preg_match($pattern, $uri)) {
return $permission;
}
}
return '';
}
private function forbidden(string $message): void
{
http_response_code(403);
header('Content-Type: application/json');
echo json_encode([
'code' => 403,
'message' => $message,
'timestamp' => time()
]);
exit;
}
}
7.3 安全防护
// app/Security/SecurityManager.php
<?php
namespace App\Security;
class SecurityManager
{
/**
* 防止SQL注入(使用PDO预处理语句即可)
*/
/**
* 防止XSS攻击
*/
public static function sanitizeInput(string $input): string
{
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
/**
* 防止CSRF攻击
*/
public static function generateCsrfToken(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function verifyCsrfToken(string $token): bool
{
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
/**
* 限制请求频率(Rate Limiting)
*/
public static function rateLimit(string $key, int $limit, int $window): bool
{
$redis = new \Redis();
$redis->connect($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT']);
$current = $redis->incr($key);
if ($current == 1) {
$redis->expire($key, $window);
}
return $current <= $limit;
}
/**
* 输入验证
*/
public static function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public static function validateUrl(string $url): bool
{
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
/**
* 防止路径遍历
*/
public static function sanitizePath(string $path): string
{
return preg_replace('/\.\./', '', $path);
}
/**
* 安全Headers
*/
public static function setSecurityHeaders(): void
{
// 防止XSS
header('X-XSS-Protection: 1; mode=block');
// 防止点击劫持
header('X-Frame-Options: SAMEORIGIN');
// MIME类型嗅探防护
header('X-Content-Type-Options: nosniff');
// 内容安全策略
header("Content-Security-Policy: default-src 'self'");
// 严格传输安全(HTTPS)
if ($_SERVER['HTTPS'] ?? false) {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
// 引荐来源策略
header('Referrer-Policy: strict-origin-when-cross-origin');
}
}
第八部分:性能优化与缓存
8.1 缓存服务
// app/Services/CacheService.php
<?php
namespace App\Services;
use Redis;
class CacheService
{
private Redis $redis;
private string $prefix;
private int $defaultTtl;
public function __construct(string $prefix = 'app:', int $defaultTtl = 3600)
{
$this->redis = new Redis();
$this->redis->connect(
$_ENV['REDIS_HOST'] ?? 'localhost',
$_ENV['REDIS_PORT'] ?? 6379
);
if (!empty($_ENV['REDIS_PASSWORD'])) {
$this->redis->auth($_ENV['REDIS_PASSWORD']);
}
$this->prefix = $prefix;
$this->defaultTtl = $defaultTtl;
}
public function get(string $key)
{
$value = $this->redis->get($this->prefix . $key);
return $value ? json_decode($value, true) : null;
}
public function set(string $key, $value, ?int $ttl = null): bool
{
$data = json_encode($value);
$ttl = $ttl ?? $this->defaultTtl;
return $this->redis->setex($this->prefix . $key, $ttl, $data);
}
public function delete(string $key): int
{
return $this->redis->del($this->prefix . $key);
}
public function exists(string $key): bool
{
return $this->redis->exists($this->prefix . $key) > 0;
}
public function remember(string $key, callable $callback, ?int $ttl = null)
{
if ($this->exists($key)) {
return $this->get($key);
}
$value = $callback();
$this->set($key, $value, $ttl);
return $value;
}
public function flush(): bool
{
$keys = $this->redis->keys($this->prefix . '*');
foreach ($keys as $key) {
$this->redis->del($key);
}
return true;
}
public function increment(string $key, int $amount = 1): int
{
return $this->redis->incrBy($this->prefix . $key, $amount);
}
public function decrement(string $key, int $amount = 1): int
{
return $this->redis->decrBy($this->prefix . $key, $amount);
}
}
8.2 数据库查询缓存
// app/Core/Database/CachedQueryBuilder.php
<?php
namespace App\Core\Database;
use App\Services\CacheService;
class CachedQueryBuilder extends QueryBuilder
{
private CacheService $cache;
private int $cacheTtl;
private bool $shouldCache = true;
public function __construct(string $modelClass, ?int $cacheTtl = 3600)
{
parent::__construct($modelClass);
$this->cache = new CacheService('db_query:');
$this->cacheTtl = $cacheTtl;
}
public function get(): array
{
if (!$this->shouldCache) {
return parent::get();
}
$cacheKey = $this->generateCacheKey();
return $this->cache->remember($cacheKey, function() {
return parent::get();
}, $this->cacheTtl);
}
public function first(): ?object
{
if (!$this->shouldCache) {
return parent::first();
}
$cacheKey = $this->generateCacheKey() . ':first';
return $this->cache->remember($cacheKey, function() {
return parent::first();
}, $this->cacheTtl);
}
public function count(): int
{
if (!$this->shouldCache) {
return parent::count();
}
$cacheKey = $this->generateCacheKey() . ':count';
return $this->cache->remember($cacheKey, function() {
return parent::count();
}, $this->cacheTtl);
}
public function disableCache(): self
{
$this->shouldCache = false;
return $this;
}
public function enableCache(): self
{
$this->shouldCache = true;
return $this;
}
public function ttl(int $seconds): self
{
$this->cacheTtl = $seconds;
return $this;
}
private function generateCacheKey(): string
{
// 生成基于SQL和绑定的缓存键
$sql = $this->buildSql();
$key = md5($sql . json_encode($this->bindings));
return $key;
}
private function buildSql(): string
{
$sql = "SELECT * FROM {$this->table}";
if (!empty($this->wheres)) {
$sql .= " WHERE " . $this->buildWhereClause();
}
if (!empty($this->orderBy)) {
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
}
if ($this->limit !== null) {
$sql .= " LIMIT {$this->limit}";
}
if ($this->offset !== null) {
$sql .= " OFFSET {$this->offset}";
}
return $sql;
}
}