GateRequest.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <?php
  2. namespace App\Http\Requests;
  3. use App\Services\MenuService;
  4. use Illuminate\Foundation\Http\FormRequest;
  5. class GateRequest extends FormRequest
  6. {
  7. /**
  8. * @var string
  9. */
  10. protected $currentRoute;
  11. /**
  12. * @var array
  13. */
  14. protected $tracks;
  15. /**
  16. * 通用鉴权
  17. *
  18. * @return bool
  19. */
  20. public function authorize():bool
  21. {
  22. if (!$this->ajax() && !$this->authorizeGate())$this->sameOriginMatching();
  23. return true;
  24. }
  25. public function rules():array
  26. {
  27. return [];
  28. }
  29. /**
  30. * 授权门鉴定
  31. *
  32. * @return bool
  33. */
  34. public function authorizeGate():bool
  35. {
  36. $this->currentRoute = ltrim($this->getPathInfo(),"/");
  37. /** @var MenuService $service */
  38. $service = app("MenuService");
  39. if (!$this->matchingRoute($service->getVisibleMenuListAll()))return false;//全量匹配失败直接授权失败 此时正确做法应该是404
  40. $correctStack = $this->tracks; //全量匹配下的 正确栈节点
  41. $this->tracks = [];//栈节点制空 存储目标节点
  42. if (!$this->matchingRoute($service->getVisibleFunctionList()))return false;//部分匹配下的授权失败
  43. return count($correctStack)==count($this->tracks); //正确栈与目标栈 对比
  44. }
  45. /**
  46. * 递归匹配路由
  47. *
  48. * 每次递归 各自记录自己的栈路径
  49. * 每次匹配成功都将当前栈路径替换全局栈路径
  50. * 替换后如果存在节点继续匹配 直至节点为空得出最终的栈终点
  51. */
  52. private function matchingRoute(array $routes,array $tagStack = [],bool $successMark = false):bool
  53. {
  54. foreach ($routes as $route){
  55. $currentStack = $tagStack;$currentStack[] = $route["name"];
  56. $match = $this->matching($route["route"] ?? "");
  57. if ($match){$this->tracks = $currentStack;$successMark = true;}
  58. if ($route["child"] ?? false){
  59. if ($this->matchingRoute($route["child"],$currentStack,$successMark))return true;
  60. }
  61. }
  62. return $successMark;
  63. }
  64. private function matching($tag):bool
  65. {
  66. if (!$tag)return false;
  67. $tag = explode("?",$tag)[0];//去除参数
  68. if ($this->currentRoute == $tag)return true; //相等
  69. if (strpos($tag,"*")===false)return false;//无泛匹配符
  70. $cr = explode("/",$this->currentRoute);
  71. $tr = explode("/",$tag);
  72. $crLen = count($cr);
  73. $trLen = count($tr);
  74. if ($crLen<$trLen)return false;
  75. if ($crLen>$trLen && $tr[$trLen-1]=='*'){
  76. $cr = array_slice($cr,0,$trLen);
  77. $crLen = $trLen;
  78. }
  79. if ($crLen!=$trLen)return false;
  80. foreach ($tr as $index=>$item){
  81. if ($item=='*')$tr[$index] = $cr[$index];
  82. }
  83. return implode("/",$cr) == implode("/",$tr);
  84. }
  85. /**
  86. * 递归一个同源节点 返回可用URL 深度取决于$this->tracks的长度
  87. *
  88. * @param array $routes
  89. * @param int $depth
  90. *
  91. * @return string|null
  92. */
  93. private function searchOrigin(array $routes,int $depth = 0):?string
  94. {
  95. $url = null;//预设返回URL
  96. foreach ($routes as $route){
  97. //如果目标栈长度大于当前深度 且 当前节点与目标节点name相同 且 当前节点有子节点存在,递归进入子节点,深度加1
  98. if (count($this->tracks)>$depth &&
  99. $this->tracks[$depth]==$route["name"] &&
  100. ($route["child"] ?? false))
  101. {
  102. $currentChildUrl = $this->searchOrigin($route["child"],$depth+1);
  103. if ($currentChildUrl)return $currentChildUrl;
  104. }
  105. if (!$url && ($route["route"] ?? ""))$url = $route["route"]; //取最近的同源的URL
  106. }
  107. return $url;
  108. }
  109. /**
  110. * 同源匹配 此时已经拿到同源节点 $this->tracks 需要在此节点上寻找一个允许的权限
  111. */
  112. public function sameOriginMatching()
  113. {
  114. $url = null;
  115. //目标节点存在
  116. if ($this->tracks)$url = $this->searchOrigin(app("MenuService")->getVisibleFunctionList());
  117. if (!$url)$url = 'denied';
  118. header('Location: /'.$url,true,301);
  119. die();
  120. //目标节点为空的话说明一级节点都无权访问
  121. // 此时返回一级同源节点就无意义 会产生误导性,所以前往无权页面
  122. //正常情况下这个预设不会被正常用户触发
  123. }
  124. }