安全指南

最后更新: 2026-01-27 11:02:47

安全指南

本文档提供了 Unicode Framework 的安全最佳实践和常见安全问题的解决方案。

认证与授权

使用 JWT 认证

// ✅ 推荐:使用框架提供的 JWT 认证
use Unicode\Framework\Auth\JWT;

$jwt = new JWT($secret); $token = $jwt->encode(['user_id' => $userId]);

// 验证 Token try { $payload = $jwt->decode($token); } catch (\Exception $e) { // Token 无效 }

Token 安全存储

// ✅ 推荐:使用 HttpOnly Cookie
setcookie('token', $token, [
    'httponly' => true,  // 防止 XSS 攻击
    'secure' => true,    // 仅 HTTPS
    'samesite' => 'Strict',
]);

// ❌ 错误:存储在 localStorage(容易被 XSS 攻击) localStorage.setItem('token', token);

密码哈希

// ✅ 推荐:使用 password_hash
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

// 验证密码 if (password_verify($password, $hashedPassword)) { // 密码正确 }

// ❌ 错误:使用 md5 或 sha1 $hashedPassword = md5($password); // 不安全

数据验证

输入验证

// ✅ 推荐:验证所有用户输入
use Unicode\Framework\Validation\Validator;

$validator = Validator::make($request->all(), [ 'email' => 'required|email', 'age' => 'required|integer|min:18', 'name' => 'required|string|max:255', ]);

if (!$validator->validate()) { return Response::json(['errors' => $validator->errors()], 422); }

// ❌ 错误:不验证输入 $email = $request->post('email'); // 可能包含恶意数据

输出转义

// ✅ 推荐:在模板中自动转义
// Smarty 模板
{$user.bio|escape:'html'}

// ❌ 错误:直接输出未转义的数据 echo $user->bio; // 可能包含 XSS 代码

SQL 注入防护

使用查询构建器

// ✅ 推荐:使用查询构建器(自动参数绑定)
$users = db('users')
    ->where('email', $email)
    ->where('status', $status)
    ->get();

// ❌ 错误:直接拼接 SQL $sql = "SELECT * FROM users WHERE email = '{$email}'"; $users = db()->query($sql); // SQL 注入风险

使用参数绑定

// ✅ 推荐:使用参数绑定
$users = db()->query(
    'SELECT * FROM users WHERE email = ? AND status = ?',
    [$email, $status]
);

// ❌ 错误:字符串拼接 $sql = "SELECT * FROM users WHERE email = '{$email}'";

使用 ORM

// ✅ 推荐:使用 ORM(自动防护)
$user = User::where('email', $email)->first();

// ❌ 错误:原生 SQL 拼接 $user = db()->query("SELECT * FROM users WHERE email = '{$email}'");

XSS 防护

模板自动转义

// ✅ 推荐:在配置中启用自动转义
// config/view.php
return [
    'engines' => [
        'smarty' => [
            'escape_html' => true,  // 自动转义 HTML
        ],
    ],
];

// 模板中 {$user.bio} // 自动转义

手动转义

// ✅ 推荐:需要输出 HTML 时手动转义
$safeHtml = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
echo $safeHtml;

// ❌ 错误:直接输出用户输入 echo $userInput; // XSS 风险

白名单过滤

// ✅ 推荐:使用白名单过滤 HTML
use HTMLPurifier;

$config = HTMLPurifier_Config::createDefault(); $purifier = new HTMLPurifier($config); $cleanHtml = $purifier->purify($userInput);

CSRF 防护

生成 CSRF Token

// 生成 CSRF Token
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;

// 在表单中 <input type="hidden" name="csrf_token" value="<?= $token ?>">

验证 CSRF Token

// 验证 CSRF Token
if ($request->post('csrf_token') !== $_SESSION['csrf_token']) {
    return Response::json(['error' => 'Invalid CSRF token'], 403);
}

使用框架中间件

// 创建 CSRF 中间件
class CsrfMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, callable $next): Response
    {
        if ($request->method() === 'POST') {
            $token = $request->post('csrf_token');
            if ($token !== $_SESSION['csrf_token']) {
                return Response::json(['error' => 'Invalid CSRF token'], 403);
            }
        }

return $next($request); } }

密码安全

密码哈希

// ✅ 推荐:使用 password_hash
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

// 验证密码 if (password_verify($password, $hashedPassword)) { // 密码正确 }

密码强度要求

// ✅ 推荐:验证密码强度
use Unicode\Framework\Validation\Rules\Custom;

$rules = [ 'password' => [ 'required', 'string', 'min:8', new Custom(function($value) { return preg_match('/[A-Z]/', $value) && preg_match('/[a-z]/', $value) && preg_match('/[0-9]/', $value); }, '密码必须包含大小写字母和数字'), ], ];

密码重置

// ✅ 推荐:使用安全的密码重置流程
// 1. 生成随机 Token
$token = bin2hex(random_bytes(32));

// 2. 存储 Token(带过期时间) db('password_resets')->insert([ 'email' => $email, 'token' => hash('sha256', $token), 'expires_at' => date('Y-m-d H:i:s', time() + 3600), ]);

// 3. 发送重置链接 $resetLink = "https://example.com/reset-password?token={$token}";

// 4. 验证 Token $reset = db('password_resets') ->where('token', hash('sha256', $token)) ->where('expires_at', '>', date('Y-m-d H:i:s')) ->first();

文件上传安全

文件类型验证

// ✅ 推荐:验证文件类型
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$uploadedFile = $request->file('avatar');

if (!in_array($uploadedFile->getMimeType(), $allowedTypes)) { return Response::json(['error' => 'Invalid file type'], 422); }

文件大小限制

// ✅ 推荐:限制文件大小
$maxSize = 5  1024  1024; // 5MB

if ($uploadedFile->getSize() > $maxSize) { return Response::json(['error' => 'File too large'], 422); }

安全存储

// ✅ 推荐:使用安全的文件名和存储路径
$filename = bin2hex(random_bytes(16)) . '.' . $extension;
$path = storage_path('uploads/' . date('Y/m/d') . '/' . $filename);

// 不要使用用户提供的文件名 // ❌ $filename = $request->post('filename');

文件内容验证

// ✅ 推荐:验证文件内容(不仅仅是扩展名)
$imageInfo = getimagesize($uploadedFile->getPath());
if ($imageInfo === false) {
    return Response::json(['error' => 'Invalid image'], 422);
}

环境配置

环境变量

// ✅ 推荐:敏感信息使用环境变量
// .env
DB_PASSWORD=your-secure-password
JWT_SECRET=your-secret-key
API_KEY=your-api-key

// 代码中 $password = getenv('DB_PASSWORD'); $secret = getenv('JWT_SECRET');

// ❌ 错误:硬编码敏感信息 $password = 'my-password'; // 不安全

配置文件安全

// ✅ 推荐:配置文件不包含敏感信息
// config/database.php
return [
    'password' => getenv('DB_PASSWORD'),  // 从环境变量读取
];

// ❌ 错误:配置文件包含敏感信息 return [ 'password' => 'my-password', // 不安全 ];

.env 文件保护

<h1 id="推荐env-文件不提交到版本控制">✅ 推荐:.env 文件不提交到版本控制</h1>
<h1 id="gitignore">.gitignore</h1>
.env
.env.local
.env.*.local

最佳实践

1. 最小权限原则

// ✅ 推荐:只授予必要的权限
// 用户只能访问自己的数据
$user = User::find($userId);
if ($user->id !== $currentUserId) {
    return Response::json(['error' => 'Forbidden'], 403);
}

2. 输入验证和输出转义

// ✅ 推荐:验证输入,转义输出
// 1. 验证输入
$validator = Validator::make($data, $rules);

// 2. 处理数据 $user = User::create($validator->getValidated());

// 3. 转义输出 return view('user/profile', [ 'user' => $user, // 模板自动转义 ]);

3. 使用 HTTPS

// ✅ 推荐:生产环境使用 HTTPS
if (getenv('APP_ENV') === 'production') {
    if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
        header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
        exit;
    }
}

4. 错误处理

// ✅ 推荐:不暴露敏感错误信息
try {
    // 操作
} catch (\Exception $e) {
    // 开发环境:显示详细错误
    if (getenv('APP_ENV') === 'development') {
        error_log($e->getMessage());
    }

// 生产环境:显示通用错误 return Response::json(['error' => 'An error occurred'], 500); }

5. 日志记录

// ✅ 推荐:记录安全相关事件
use Unicode\Framework\Log\LogManager;

$logger = LogManager::getInstance();

// 记录登录失败 $logger->warning('Login failed', [ 'email' => $email, 'ip' => $request->ip(), ]);

// 记录敏感操作 $logger->info('Password changed', [ 'user_id' => $userId, 'ip' => $request->ip(), ]);

常见安全问题

SQL 注入

问题:直接拼接 SQL 语句 解决方案:使用查询构建器或参数绑定

XSS 攻击

问题:直接输出用户输入 解决方案:模板自动转义或手动转义

CSRF 攻击

问题:没有验证请求来源 解决方案:使用 CSRF Token

密码泄露

问题:使用弱密码或明文存储 解决方案:使用 password_hash 和强密码策略

文件上传漏洞

问题:不验证文件类型和内容 解决方案:验证文件类型、大小和内容

相关文档