-
背景
最近在对接某大厂,部署差不多了,但是在漏洞扫描环节有问题,前端是用vue代码写的。后端是php。发现前端路由可以拦截未登录的url。但是后端php接口不用登录就能访问,很危险 -
解决方法
一、创建 Auth 中间件
首先创建一个专门用于验证 Session 的中间件: 里面可以放开不用登录的接口
<?php
// 目录位置 app/middleware/Auth.php
namespace app\middleware;use think\facade\Session;
use think\Response;class Auth
{// 定义不需要验证的接口路径protected $exceptPaths = ['Auth/login', // 登录接口'Auth/logout', // 注册接口'Auth/getCode', // 注册接口'Index/getTitle', // 注册接口// 可添加更多无需验证的接口...];public function handle($request, \Closure $next){// 获取当前请求的路径(不含域名和参数)$path = $request->pathinfo();// 检查是否为排除的路径if ($this->shouldPassThrough($path)) {return $next($request); // 跳过验证,直接继续}// 检查Session中是否有登录用户信息if (!Session::has(SESSION_LOGIN_KEY)) {// 未登录,返回JSON错误响应return json(['code' => 0,'msg' => '请先登录','data' => 401,'url' => null,'wait' => 3]);}// 已登录,将用户信息注入请求对象,方便后续使用$request->user = Session::get(SESSION_LOGIN_KEY);// 继续执行后续请求处理return $next($request);}// 判断请求是否应跳过验证protected function shouldPassThrough($path){foreach ($this->exceptPaths as $except) {if (strpos($path, $except) === 0) {return true;}}return false;}
}
二、注册中间件
有两种方式注册中间件,根据你的需求选择:
- 全局中间件(所有请求都验证)
打开 目录位置app/middleware.php 文件,添加中间件:
// app/middleware.php
return [// 其他中间件...\app\middleware\Auth::class,
];
- 路由中间件(按需验证)
如果你只想验证部分接口,在路由定义中使用中间件:
// app/route/route.php
use think\facade\Route;// 应用Auth中间件到整个Video控制器组
Route::group('video', function () {Route::get('getIqiyiLists', 'Video/getIqiyiLists');Route::get('getSohuLists', 'Video/getSohuLists');// 其他接口...
})->middleware(\app\middleware\Auth::class);// 或者只应用到特定接口
Route::get('video/getIqiyiLists', 'Video/getIqiyiLists')->middleware(\app\middleware\Auth::class);
--------------------------------分割线------------------------------
针对ThinkPHP 3.2.x 老版本的实现
行为扩展的实现步骤
- 创建行为类文件
在项目目录下创建以下文件:
/application/Common/Behavior- CheckLoginBehavior.php # 行为类文件/Conf- behavior.php # 行为配置文件
- 编写行为类代码
CheckLoginBehavior.php 文件内容:
<?php
namespace Common\Behavior;
use Think\Behavior;class CheckLoginBehavior extends Behavior {public function run(&$params) {// 检查是否登录if(empty(session(SESSION_LOGIN_KEY))) {// 获取当前请求的控制器和操作$controller = CONTROLLER_NAME;$action = ACTION_NAME;// 定义白名单(无需登录即可访问的控制器/操作)$whiteList = ['Auth/login','Auth/logout','User/login','User/logout','User/getCode'];// 检查当前请求是否在白名单中$currentUrl = "$controller/$action";if(!in_array($currentUrl, $whiteList)) {// 设置响应头为JSON格式header('Content-Type:application/json;charset=utf-8');// 输出统一的错误响应exit(json_encode(['code' => 401,'msg' => '未登录,请先登录','data' => null]));}}}
}
- 配置行为扩展
在 application/Conf/behavior.php 中添加以下内容:
<?php
// 行为扩展配置
return [// 在应用结束时执行登录检查'app_end' => ['Common\\Behavior\\CheckLoginBehavior']
];
行为扩展增加XSS
也是在行为扩展里面增加的
<?php
// application/Behavior/XssCheckBehavior.php
namespace Behavior;
use Think\Behavior;class XssCheckBehavior extends Behavior {// 行为执行入口public function run(&$params) {// 只对POST请求进行XSS检测if ($_SERVER['REQUEST_METHOD'] === 'POST') {// 获取原始请求数据$rawData = file_get_contents('php://input');// 强制解析JSON数据(无论Content-Type如何)$postData = json_decode($rawData, true);// 检查JSON解析是否成功if (json_last_error() !== JSON_ERROR_NONE || empty($postData)) {// JSON解析失败,尝试从$_POST获取$postData = $_POST;// 如果$_POST也为空,使用原始数据if (empty($postData)) {$postData = ['raw_data' => $rawData];}}// 检查数据if (!empty($postData) && !$this->checkPostData($postData)) {$this->returnError();}}}// 检查POST数据private function checkPostData($data, $parentKey = '') {foreach ($data as $key => $value) {$fullKey = $parentKey ? "{$parentKey}[{$key}]" : $key;if (is_array($value)) {// 递归检查数组if (!$this->checkPostData($value, $fullKey)) {return false;}} else {// 检查字符串值if ($this->containsXss($value)) {\Think\Log::record("XSS检测到风险: {$fullKey} => {$value}", 'ERR');return false;}}}return true;}// 检测是否包含XSS风险private function containsXss($str) {// 空值不检测if (empty($str)) {return false;}// 检测常见的XSS模式$patterns = ['/<script/i', // 直接的script标签'/<[^>]*on\w+ *=/i', // 事件处理函数'/javascript:/i', // javascript协议'/vbscript:/i', // vbscript协议'/<iframe/i', // iframe标签'/<style/i', // style标签'/<meta/i', // meta标签'/<link/i', // link标签'/data:text\/html/i', // data URI'/<svg/i', // SVG注入'/&#[xX]?[0-9a-fA-F]+;/i', // HTML实体编码];foreach ($patterns as $pattern) {if (preg_match($pattern, $str)) {return true;}}return false;}// 返回错误信息private function returnError() {if (IS_AJAX) {// AJAX请求返回JSON错误$result = ['status' => 0,'info' => '提交内容包含不安全字符,请检查!'];exit(json_encode($result));} else {// 普通请求跳转$this->error('提交内容包含不安全字符,请检查!');}}
}
- 总结
大概就是这样玩的。ThinkPHP 的中间件机制还是挺不错。后续你还可以加入权限的控制之类的。如果发现博文有问题,欢迎老鸟指点一二