本文目录导读:

在ThinkPHP项目中防御点击劫持(Clickjacking)攻击,可以通过以下几种方式实现:
HTTP响应头防御(最常用)
使用中间件方式(推荐)
在 app/middleware.php 中注册全局中间件:
<?php
// app/middleware.php
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
// \think\middleware\SessionInit::class,
// 添加防点击劫持中间件
\app\middleware\ClickjackingProtection::class,
];
创建中间件 app/middleware/ClickjackingProtection.php:
<?php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
class ClickjackingProtection
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 设置X-Frame-Options响应头
$response->header([
'X-Frame-Options' => 'SAMEORIGIN', // 或者 'DENY'
'Content-Security-Policy' => "frame-ancestors 'self'",
]);
return $response;
}
}
在控制器中动态设置
<?php
namespace app\controller;
use think\Request;
class Index
{
public function index(Request $request)
{
// 设置防iframe加载
header('X-Frame-Options: SAMEORIGIN');
header("Content-Security-Policy: frame-ancestors 'self'");
return view();
}
}
使用路由中间件
在 route/app.php 中对特定路由添加中间件:
<?php
use think\facade\Route;
// 给特定路由添加防点击劫持中间件
Route::group('admin', function () {
Route::get('dashboard', 'admin/Dashboard/index');
Route::post('data/save', 'admin/Data/save');
})->middleware(\app\middleware\ClickjackingProtection::class);
Nginx层防御(服务器配置)
在 Nginx 配置中添加:
server {
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
Apache层防御
在 .htaccess 或虚拟主机配置中添加:
Header always append X-Frame-Options SAMEORIGIN Header set Content-Security-Policy "frame-ancestors 'self'"
前端页面防御(JavaScript方式)
在项目入口文件或公共模板中添加:
<!-- 在公共模板文件如 public/header.html 中添加 -->
<script>
// 防止页面被iframe加载
if (window.top !== window.self) {
window.top.location = window.self.location;
// 或者直接显示警告
// document.body.innerHTML = "该页面不允许被嵌入iframe中";
}
</script>
或者在公共控制器中:
<?php
namespace app\common;
class BaseController
{
protected function preventClickjacking()
{
// 判断是否为iframe环境
if (isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] === 'iframe') {
// 返回错误页面或跳转
return redirect('/');
}
}
}
完整的中间件实现(含多策略)
<?php
namespace app\middleware;
use Closure;
use think\Request;
use think\Response;
class ClickjackingProtection
{
/**
* 防御策略
* allow - 允许所有
* deny - 禁止所有
* sameorigin - 仅允许同源
* whitelist - 白名单
*/
protected $strategy = 'sameorigin';
// 白名单域名
protected $whitelist = [
'example.com',
'admin.example.com'
];
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 检查来源
$origin = $request->header('origin', '');
$referer = $request->header('referer', '');
switch ($this->strategy) {
case 'deny':
$response->header('X-Frame-Options', 'DENY');
$response->header('Content-Security-Policy', "frame-ancestors 'none'");
break;
case 'sameorigin':
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header('Content-Security-Policy', "frame-ancestors 'self'");
break;
case 'whitelist':
if ($this->checkWhitelist($origin, $referer)) {
$response->header('X-Frame-Options', 'ALLOW-FROM ' . $origin);
} else {
$response->header('X-Frame-Options', 'DENY');
$response->header('Content-Security-Policy', "frame-ancestors 'none'");
}
break;
}
// 添加其他安全头
$response->header([
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1; mode=block',
]);
return $response;
}
private function checkWhitelist($origin, $referer)
{
$url = $origin ?: $referer;
if (empty($url)) return true;
$host = parse_url($url, PHP_URL_HOST);
if (!$host) return false;
foreach ($this->whitelist as $domain) {
if ($host === $domain || str_ends_with($host, '.' . $domain)) {
return true;
}
}
return false;
}
// 设置策略的方法
public function setStrategy($strategy)
{
$this->strategy = $strategy;
return $this;
}
}
配置文件方式
在 config/safe.php 中新增配置:
<?php
return [
// 点击劫持防御
'clickjacking' => [
'enable' => true,
'x_frame_options' => 'SAMEORIGIN', // DENY, SAMEORIGIN, 或自定义地址
'csp_frame_ancestors' => "'self'", // 或具体域名列表
],
];
然后在中间件中读取配置:
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$config = config('safe.clickjacking');
if ($config['enable']) {
$response->header('X-Frame-Options', $config['x_frame_options']);
$response->header('Content-Security-Policy',
"frame-ancestors {$config['csp_frame_ancestors']}");
}
return $response;
}
推荐使用组合防御
建议采用多层防御策略:
- Nginx/Apache层:设置基础响应头
- ThinkPHP中间件层:应用级别的防御逻辑
- 前端JavaScript:提供额外保护
这样即使某一层被绕过,其他层还能提供保护,需要根据项目实际需求选择合适的防御级别,对于后台管理页面建议使用DENY或SAMEORIGIN策略。