| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- <?php
- namespace App\Http\Requests;
- use App\Services\MenuService;
- use Illuminate\Foundation\Http\FormRequest;
- use Illuminate\Support\Facades\Auth;
- use Illuminate\Support\Facades\Log;
- class GateRequest extends FormRequest
- {
- /**
- * @var string
- */
- protected $currentRoute;
- /**
- * @var array
- */
- protected $tracks;
- /**
- * 通用鉴权
- *
- * @return bool
- */
- public function authorize():bool
- {
- if (!$this->ajax() && !$this->authorizeGate())$this->sameOriginMatching();
- return true;
- }
- public function rules():array
- {
- return [];
- }
- /**
- * 授权门鉴定
- *
- * @return bool
- */
- public function authorizeGate():bool
- {
- $this->currentRoute = ltrim($this->getPathInfo(),"/");
- /** @var MenuService $service */
- $service = app("MenuService");
- if (!$this->matchingRoute($service->getVisibleMenuListAll()))return false;//全量匹配失败直接授权失败 此时正确做法应该是404
- $correctStack = $this->tracks; //全量匹配下的 正确栈节点
- $this->tracks = [];//栈节点制空 存储目标节点
- if (!$this->matchingRoute($service->getVisibleFunctionList()))return false;//部分匹配下的授权失败
- return count($correctStack)==count($this->tracks); //正确栈与目标栈 对比
- }
- /**
- * 递归匹配路由
- *
- * 每次递归 各自记录自己的栈路径
- * 每次匹配成功都将当前栈路径替换全局栈路径
- * 替换后如果存在节点继续匹配 直至节点为空得出最终的栈终点
- */
- private function matchingRoute(array $routes,array $tagStack = [],bool $successMark = false):bool
- {
- foreach ($routes as $route){
- $currentStack = $tagStack;$currentStack[] = $route["name"];
- $match = $this->matching($route["route"] ?? "");
- if ($match){$this->tracks = $currentStack;$successMark = true;}
- if ($route["child"] ?? false){
- if ($this->matchingRoute($route["child"],$currentStack,$successMark))return true;
- }
- }
- return $successMark;
- }
- private function matching($tag):bool
- {
- if (!$tag)return false;
- $tag = explode("?",$tag)[0];//去除参数
- if ($this->currentRoute == $tag)return true; //相等
- if (strpos($tag,"*")===false)return false;//无泛匹配符
- $cr = explode("/",$this->currentRoute);
- $tr = explode("/",$tag);
- $crLen = count($cr);
- $trLen = count($tr);
- if ($crLen<$trLen)return false;
- if ($crLen>$trLen && $tr[$trLen-1]=='*'){
- $cr = array_slice($cr,0,$trLen);
- $crLen = $trLen;
- }
- if ($crLen!=$trLen)return false;
- foreach ($tr as $index=>$item){
- if ($item=='*')$tr[$index] = $cr[$index];
- }
- return implode("/",$cr) == implode("/",$tr);
- }
- /**
- * 递归一个同源节点 返回可用URL 深度取决于$this->tracks的长度
- *
- * @param array $routes
- * @param int $depth
- *
- * @return string|null
- */
- private function searchOrigin(array $routes,int $depth = 0):?string
- {
- $url = null;//预设返回URL
- foreach ($routes as $route){
- //如果目标栈长度大于当前深度 且 当前节点与目标节点name相同 且 当前节点有子节点存在,递归进入子节点,深度加1
- if (count($this->tracks)>$depth &&
- $this->tracks[$depth]==$route["name"] &&
- ($route["child"] ?? false))
- {
- $currentChildUrl = $this->searchOrigin($route["child"],$depth+1);
- if ($currentChildUrl)return $currentChildUrl;
- }
- if (!$url && ($route["route"] ?? ""))$url = $route["route"]; //取最近的同源的URL
- }
- return $url;
- }
- /**
- * 同源匹配 此时已经拿到同源节点 $this->tracks 需要在此节点上寻找一个允许的权限
- */
- public function sameOriginMatching()
- {
- $url = null;
- //目标节点存在
- if ($this->tracks){
- $url = $this->searchOrigin(app("MenuService")->getVisibleFunctionList());
- if ($url)Log::notice("同源策略跳转",["user"=>Auth::id(),"url"=>$url,"currentRoute"=>$this->currentRoute]);
- }
- if (!$url)$url = 'denied';
- header('Location: /'.$url,true,302);
- die();
- //目标节点为空的话说明一级节点都无权访问
- // 此时返回一级同源节点就无意义 会产生误导性,所以前往无权页面
- //正常情况下这个预设不会被正常用户触发
- }
- }
|