本文目录导读:

我来详细介绍ThinkPHP项目中实施CSP(内容安全策略)的方法:
CSP基础配置
1 通过中间件添加CSP头
在app/middleware.php中注册CSP中间件:
<?php
// app/middleware.php
return [
// 全局CSP中间件
\app\middleware\CspMiddleware::class,
];
2 创建CSP中间件
<?php
// app/middleware/CspMiddleware.php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
class CspMiddleware
{
/**
* CSP策略配置
*/
protected $cspConfig = [
'default-src' => "'self'",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline' https://cdn.example.com",
'img-src' => "'self' data: https://*.example.com",
'font-src' => "'self' https://fonts.googleapis.com https://fonts.gstatic.com",
'connect-src' => "'self'",
'frame-src' => "'self'",
'object-src' => "'none'",
'base-uri' => "'self'",
'form-action' => "'self'",
'frame-ancestors' => "'self'",
'upgrade-insecure-requests' => '',
];
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 构建CSP头部
$cspHeader = $this->buildCspHeader();
// 添加CSP响应头
$response->header([
'Content-Security-Policy' => $cspHeader,
'X-Content-Security-Policy' => $cspHeader, // 旧浏览器兼容
'X-WebKit-CSP' => $cspHeader, // WebKit浏览器兼容
]);
return $response;
}
/**
* 构建CSP头部字符串
*/
protected function buildCspHeader(): string
{
$directives = [];
foreach ($this->cspConfig as $key => $value) {
if ($value !== '') {
$directives[] = "{$key} {$value}";
} else {
$directives[] = $key;
}
}
return implode('; ', $directives);
}
}
动态CSP配置
1 配置文件方式
<?php
// config/csp.php
return [
// 开发环境配置
'development' => [
'default-src' => "'self'",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline'",
'img-src' => "'self' data:",
'connect-src' => "'self' ws://localhost:*",
],
// 生产环境配置
'production' => [
'default-src' => "'self'",
'script-src' => "'self'",
'style-src' => "'self'",
'img-src' => "'self' data:",
'connect-src' => "'self'",
'font-src' => "'self' https://fonts.googleapis.com https://fonts.gstatic.com",
'object-src' => "'none'",
'base-uri' => "'self'",
'form-action' => "'self'",
'frame-ancestors' => "'self'",
'upgrade-insecure-requests' => '',
],
];
2 改进中间件支持配置
<?php
// app/middleware/CspMiddleware.php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
use think\facade\Config;
class CspMiddleware
{
protected $cspConfig = [];
protected $noncePool = [];
public function __construct()
{
// 从配置文件加载CSP设置
$environment = app()->isDebug() ? 'development' : 'production';
$this->cspConfig = Config::get('csp.' . $environment);
// 生成nonce值
$this->generateNonces();
}
/**
* 生成CSP nonce
*/
protected function generateNonces(): void
{
$this->noncePool = [
'script' => base64_encode(random_bytes(16)),
'style' => base64_encode(random_bytes(16)),
];
}
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 应用CSP头部
$this->applyCspHeaders($response);
// 注入nonce到响应
if ($response instanceof Response && !$request->isAjax()) {
$this->injectNonces($response);
}
return $response;
}
/**
* 应用CSP头
*/
protected function applyCspHeaders(Response $response): void
{
$cspHeader = $this->buildCspHeader();
$response->header([
'Content-Security-Policy' => $cspHeader,
'X-Content-Security-Policy' => $cspHeader,
'X-WebKit-CSP' => $cspHeader,
]);
}
/**
* 构建CSP头
*/
protected function buildCspHeader(): string
{
$directives = [];
foreach ($this->cspConfig as $key => $value) {
// 替换nonce占位符
if (strpos($value, '{nonce}') !== false) {
$value = str_replace('{nonce}', "'nonce-{$this->noncePool[$key] ?? ''}'", $value);
}
$directives[] = $value === '' ? $key : "{$key} {$value}";
}
return implode('; ', $directives);
}
/**
* 注入nonce到HTML
*/
protected function injectNonces(Response $response): void
{
$content = $response->getContent();
// 替换script标签
$content = preg_replace(
'/<script(?!.*nonce)/i',
'<script nonce="' . $this->noncePool['script'] . '"',
$content
);
// 替换style标签
$content = preg_replace(
'/<style(?!.*nonce)/i',
'<style nonce="' . $this->noncePool['style'] . '"',
$content
);
$response->content($content);
}
}
视图中的CSP集成
1 在模板中使用nonce
<!-- application/index/view/index/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">ThinkPHP CSP</title>
<!-- 使用nonce的样式 -->
<style nonce="<?= $csp['style'] ?>">
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
border-radius: 5px;
}
</style>
<!-- 内联脚本使用nonce -->
<script nonce="<?= $csp['script'] ?>">
document.addEventListener('DOMContentLoaded', function() {
console.log('页面加载完成');
});
</script>
</head>
<body>
<div class="container">
<h1>内容安全策略演示</h1>
<!-- 外部脚本 -->
<script src="/static/js/app.js" nonce="<?= $csp['script'] ?>"></script>
<!-- 内联事件处理器可能需要额外处理 -->
<button onclick="handleClick()" data-nonce="<?= $csp['script'] ?>">点击我</button>
</div>
</body>
</html>
2 控制器中传递nonce
<?php
// app/controller/Index.php
namespace app\controller;
use think\Request;
class Index
{
public function index(Request $request)
{
// 获取中间件生成的nonce
$csp = [
'script' => $request->middleware('nonce_script'),
'style' => $request->middleware('nonce_style'),
];
return view('index/index', ['csp' => $csp]);
}
}
高级CSP配置
1 CSP报告功能
<?php
// app/middleware/CspReportMiddleware.php
namespace app\middleware;
use Closure;
use think\Request;
class CspReportMiddleware
{
protected $reportUri = '/api/csp/report';
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 添加报告URI
$response->header('Content-Security-Policy-Report-Only',
"default-src 'self'; report-uri {$this->reportUri}");
return $response;
}
}
// app/controller/api/CspReport.php
namespace app\controller\api;
use think\Request;
use think\facade\Log;
class CspReport
{
public function report(Request $request)
{
// 获取CSP违规报告
$data = $request->post();
// 记录违规信息
Log::error('CSP Violation:', $data);
// 可以在数据库中记录
// 可以发送邮件通知
return json(['status' => 'success']);
}
}
2 按路由应用不同策略
<?php
// route/app.php
use think\facade\Route;
// 管理路由使用更严格的CSP
Route::group('admin', function () {
Route::get('dashboard', 'admin/Dashboard/index');
Route::get('settings', 'admin/Settings/index');
})->middleware(\app\middleware\StrictCspMiddleware::class);
// API路由使用CORS配置
Route::group('api', function () {
// API路由
})->middleware(\app\middleware\ApiCspMiddleware::class);
// 前端路由使用宽松CSP
Route::group('frontend', function () {
// 前端路由
})->middleware(\app\middleware\FrontendCspMiddleware::class);
3 完整的CSP配置类
<?php
// app/library/Csp/CspFactory.php
namespace app\library\Csp;
class CspFactory
{
/**
* 创建严格CSP
*/
public static function strict(): array
{
return [
'default-src' => "'none'",
'script-src' => "'self'",
'style-src' => "'self'",
'img-src' => "'self' data:",
'connect-src' => "'self'",
'font-src' => "'self'",
'object-src' => "'none'",
'frame-ancestors' => "'none'",
'base-uri' => "'self'",
'form-action' => "'self'",
];
}
/**
* 创建一般CSP
*/
public static function standard(): array
{
return [
'default-src' => "'self'",
'script-src' => "'self' 'unsafe-inline'",
'style-src' => "'self' 'unsafe-inline'",
'img-src' => "'self' data: https:",
'font-src' => "'self' https:",
'connect-src' => "'self'",
'object-src' => "'none'",
'frame-ancestors' => "'self'",
'base-uri' => "'self'",
'form-action' => "'self'",
];
}
/**
* 创建宽松CSP
*/
public static function relaxed(): array
{
return [
'default-src' => "'self' *",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval' *",
'style-src' => "'self' 'unsafe-inline' *",
'img-src' => "'self' data: blob: *",
'font-src' => "'self' data: *",
'connect-src' => "'self' *",
'object-src' => "'none'",
'frame-ancestors' => "*",
];
}
}
4 动态CSP适配器
<?php
// app/middleware/AdaptiveCspMiddleware.php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
use app\library\Csp\CspFactory;
class AdaptiveCspMiddleware
{
protected $cspConfig = [];
public function handle(Request $request, Closure $next)
{
// 根据用户代理和路由动态选择CSP策略
$this->cspConfig = $this->selectCspPolicy($request);
$response = $next($request);
// 应用CSP头
$cspHeader = $this->buildCspHeader();
$response->header('Content-Security-Policy', $cspHeader);
return $response;
}
/**
* 选择CSP策略
*/
protected function selectCspPolicy(Request $request): array
{
// 判断是否为后台管理页面
if (strpos($request->pathinfo(), 'admin') === 0) {
return CspFactory::strict();
}
// 判断是否为API请求
if (strpos($request->pathinfo(), 'api') === 0) {
return CspFactory::api();
}
// 判断是否为移动端
$userAgent = $request->header('user-agent');
if (preg_match('/Mobile|Android|iPhone/i', $userAgent)) {
return CspFactory::mobile();
}
// 默认使用标准策略
return CspFactory::standard();
}
/**
* 构建CSP头
*/
protected function buildCspHeader(): string
{
$directives = [];
foreach ($this->cspConfig as $directive => $value) {
$directives[] = "{$directive} {$value}";
}
return implode('; ', $directives);
}
}
测试和调试
1 CSP测试页面
<?php
// app/controller/CspTest.php
namespace app\controller;
use think\Request;
class CspTest
{
public function index()
{
$scripts = [
'inline' => '<script>console.log("测试内联脚本");</script>',
'external' => '<script src="/static/js/test.js"></script>',
'external_cdn' => '<script src="https://cdn.example.com/js/lib.js"></script>'
];
return view('csp/test', ['scripts' => $scripts]);
}
public function report(Request $request)
{
$report = json_decode($request->getContent(), true);
// 日志记录
trace($report, 'CSP Violation');
return json(['status' => 'ok']);
}
}
2 CSP监控配置
<?php
// config/csp_monitor.php
return [
'enabled' => true,
'log_level' => 'warning',
'report_threshold' => 10, // 每分钟报告次数阈值
'report_endpoint' => '/api/csp/report',
'allowed_scripts' => [
'https://cdn.example.com',
'https://statistics.example.com',
],
'allowed_styles' => [
'https://fonts.googleapis.com',
],
'allowed_images' => [
'https://*.example.com',
],
];
3 CSP安全检查工具
<?php
// app/library/Csp/CspValidator.php
namespace app\library\Csp;
class CspValidator
{
/**
* 验证CSP策略
*/
public static function validate(array $policy): array
{
$errors = [];
$warnings = [];
// 检查是否配置了默认策略
if (!isset($policy['default-src'])) {
$errors[] = '缺少 default-src 指令';
}
// 检查是否允许了危险的指令
foreach (['script-src', 'style-src'] as $directive) {
if (isset($policy[$directive])) {
if (strpos($policy[$directive], "'unsafe-inline'") !== false) {
$warnings[] = "{$directive} 包含 unsafe-inline";
}
if (strpos($policy[$directive], "'unsafe-eval'") !== false) {
$warnings[] = "{$directive} 包含 unsafe-eval";
}
}
}
// 检查是否配置了报告URI
if (!isset($policy['report-uri']) && !isset($policy['report-to'])) {
$warnings[] = '未配置报告URI,无法监控违规行为';
}
return [
'errors' => $errors,
'warnings' => $warnings,
'valid' => empty($errors),
];
}
}
最佳实践建议
1 部署前检查清单
<?php
// app/command/CheckCsp.php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use app\library\Csp\CspFactory;
use app\library\Csp\CspValidator;
class CheckCsp extends Command
{
protected function configure()
{
$this->setName('check:csp')
->setDescription('检查CSP配置');
}
protected function execute(Input $input, Output $output)
{
$output->writeln('开始检查CSP配置...');
// 检查所有环境的CSP配置
foreach (['development', 'production', 'test'] as $env) {
$output->writeln("检查{$env}环境:");
$config = Config::get('csp.' . $env);
$result = CspValidator::validate($config);
if (!$result['valid']) {
foreach ($result['errors'] as $error) {
$output->writeln(" <error>[错误] {$error}</error>");
}
}
foreach ($result['warnings'] as $warning) {
$output->writeln(" <comment>[警告] {$warning}</comment>");
}
if (empty($result['errors']) && empty($result['warnings'])) {
$output->writeln(" <info>[OK] 配置良好</info>");
}
}
// 检查静态资源
$this->checkStaticResources($output);
$output->writeln('CSP检查完成');
}
protected function checkStaticResources(Output $output)
{
$output->writeln('检查静态资源...');
// 检查JavaScript文件
$jsFiles = glob(root_path('public/static/js/*.js'));
foreach ($jsFiles as $file) {
$content = file_get_contents($file);
if (preg_match('/\beval\s*\(/i', $content)) {
$output->writeln(" <comment>[警告] 文件包含eval: {$file}</comment>");
}
}
}
}
2 安全建议
// 1. 不在CSP中添加过多的域名白名单 // 2. 使用nonce而不是allowlist管理内联脚本 // 3. 定期检查CSP报告 // 4. 使用Report-Only模式测试 // 5. 为不同环境设置不同的CSP策略 // 6. 考虑使用SRI(子资源完整性)
通过以上配置,你可以为ThinkPHP项目建立完善的内容安全策略,有效防范XSS攻击和数据注入攻击,记得根据实际需求调整配置,并在部署前充分测试。