Auth.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <?php
  2. namespace app\admin\library;
  3. use Throwable;
  4. use ba\Random;
  5. use think\facade\Db;
  6. use think\facade\Config;
  7. use app\admin\model\Admin;
  8. use app\common\facade\Token;
  9. use app\admin\model\AdminGroup;
  10. /**
  11. * 管理员权限类
  12. * @property int $id 管理员ID
  13. * @property string $username 管理员用户名
  14. * @property string $nickname 管理员昵称
  15. * @property string $email 管理员邮箱
  16. * @property string $mobile 管理员手机号
  17. */
  18. class Auth extends \ba\Auth
  19. {
  20. /**
  21. * 需要登录时/无需登录时的响应状态代码
  22. */
  23. public const LOGIN_RESPONSE_CODE = 303;
  24. /**
  25. * 需要登录标记 - 前台应清理 token、记录当前路由 path、跳转到登录页
  26. */
  27. public const NEED_LOGIN = 'need login';
  28. /**
  29. * 已经登录标记 - 前台应跳转到基础路由
  30. */
  31. public const LOGGED_IN = 'logged in';
  32. /**
  33. * 对象实例
  34. * @var ?Auth
  35. */
  36. protected static ?Auth $instance = null;
  37. /**
  38. * 是否登录
  39. * @var bool
  40. */
  41. protected bool $loginEd = false;
  42. /**
  43. * 错误消息
  44. * @var string
  45. */
  46. protected string $error = '';
  47. /**
  48. * Model实例
  49. * @var ?Admin
  50. */
  51. protected ?Admin $model = null;
  52. /**
  53. * 令牌
  54. * @var string
  55. */
  56. protected string $token = '';
  57. /**
  58. * 刷新令牌
  59. * @var string
  60. */
  61. protected string $refreshToken = '';
  62. /**
  63. * 令牌默认有效期
  64. * 可在 config/buildadmin.php 内修改默认值
  65. * @var int
  66. */
  67. protected int $keepTime = 86400;
  68. /**
  69. * 刷新令牌有效期
  70. * @var int
  71. */
  72. protected int $refreshTokenKeepTime = 2592000;
  73. /**
  74. * 允许输出的字段
  75. * @var array
  76. */
  77. protected array $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time'];
  78. public function __construct(array $config = [])
  79. {
  80. parent::__construct($config);
  81. $this->setKeepTime((int)Config::get('buildadmin.admin_token_keep_time'));
  82. }
  83. /**
  84. * 魔术方法-管理员信息字段
  85. * @param $name
  86. * @return mixed 字段信息
  87. */
  88. public function __get($name): mixed
  89. {
  90. return $this->model?->$name;
  91. }
  92. /**
  93. * 初始化
  94. * @access public
  95. * @param array $options 传递到 /ba/Auth 的配置信息
  96. * @return Auth
  97. */
  98. public static function instance(array $options = []): Auth
  99. {
  100. if (is_null(self::$instance)) {
  101. self::$instance = new static($options);
  102. }
  103. return self::$instance;
  104. }
  105. /**
  106. * 根据Token初始化管理员登录态
  107. * @param string $token
  108. * @return bool
  109. * @throws Throwable
  110. */
  111. public function init(string $token): bool
  112. {
  113. if ($this->loginEd) {
  114. return true;
  115. }
  116. if ($this->error) {
  117. return false;
  118. }
  119. $tokenData = Token::get($token);
  120. if (!$tokenData) {
  121. return false;
  122. }
  123. $userId = intval($tokenData['user_id']);
  124. if ($userId > 0) {
  125. $this->model = Admin::where('id', $userId)->find();
  126. if (!$this->model) {
  127. $this->setError('Account not exist');
  128. return false;
  129. }
  130. if ($this->model['status'] != '1') {
  131. $this->setError('Account disabled');
  132. return false;
  133. }
  134. $this->token = $token;
  135. $this->loginSuccessful();
  136. return true;
  137. } else {
  138. $this->setError('Token login failed');
  139. return false;
  140. }
  141. }
  142. /**
  143. * 管理员登录
  144. * @param string $username 用户名
  145. * @param string $password 密码
  146. * @param bool $keep 是否保持登录
  147. * @return bool
  148. * @throws Throwable
  149. */
  150. public function login(string $username, string $password, bool $keep = false): bool
  151. {
  152. $this->model = Admin::where('username', $username)->find();
  153. if (!$this->model) {
  154. $this->setError('Username is incorrect');
  155. return false;
  156. }
  157. if ($this->model->status == '0') {
  158. $this->setError('Account disabled');
  159. return false;
  160. }
  161. $adminLoginRetry = Config::get('buildadmin.admin_login_retry');
  162. if ($adminLoginRetry && $this->model->login_failure >= $adminLoginRetry && time() - $this->model->getData('last_login_time') < 86400) {
  163. $this->setError('Please try again after 1 day');
  164. return false;
  165. }
  166. if ($this->model->password != encrypt_password($password, $this->model->salt)) {
  167. $this->loginFailed();
  168. $this->setError('Password is incorrect');
  169. return false;
  170. }
  171. if (Config::get('buildadmin.admin_sso')) {
  172. Token::clear('admin', $this->model->id);
  173. Token::clear('admin-refresh', $this->model->id);
  174. }
  175. if ($keep) {
  176. $this->setRefreshToken($this->refreshTokenKeepTime);
  177. }
  178. $this->loginSuccessful();
  179. return true;
  180. }
  181. /**
  182. * 管理员微信密码登录
  183. * @param string $username 用户名
  184. * @param string $password 密码
  185. * @param bool $keep 是否保持登录
  186. * @return bool
  187. * @throws Throwable
  188. */
  189. public function mplogin(string $username, string $password): bool
  190. {
  191. $this->model = Admin::where('username', $username)->find();
  192. if (!$this->model) {
  193. $this->setError('Username is incorrect');
  194. return false;
  195. }
  196. if ($this->model->status == '0') {
  197. $this->setError('Account disabled');
  198. return false;
  199. }
  200. $adminLoginRetry = Config::get('buildadmin.admin_login_retry');
  201. if ($adminLoginRetry && $this->model->login_failure >= $adminLoginRetry && time() - $this->model->getData('last_login_time') < 86400) {
  202. $this->setError('Please try again after 1 day');
  203. return false;
  204. }
  205. if (Config::get('buildadmin.admin_sso')) {
  206. Token::clear('admin', $this->model->id);
  207. Token::clear('admin-refresh', $this->model->id);
  208. }
  209. if ($this->model->password != encrypt_password($password, $this->model->salt)) {
  210. $this->loginFailed();
  211. $this->setError('Password is incorrect');
  212. return false;
  213. }
  214. $this->setRefreshToken($this->refreshTokenKeepTime);
  215. $this->loginSuccessful();
  216. return true;
  217. }
  218. /**
  219. * 管理员微信扫码登录
  220. * @param string $username 用户名
  221. * @param string $password 密码
  222. * @param bool $keep 是否保持登录
  223. * @return bool
  224. * @throws Throwable
  225. */
  226. public function wxlogin(string $username, string $password): bool
  227. {
  228. $this->model = Admin::where('username', $username)->find();
  229. if (!$this->model) {
  230. $this->setError('Username is incorrect');
  231. return false;
  232. }
  233. if ($this->model->status == '0') {
  234. $this->setError('Account disabled');
  235. return false;
  236. }
  237. $adminLoginRetry = Config::get('buildadmin.admin_login_retry');
  238. if ($adminLoginRetry && $this->model->login_failure >= $adminLoginRetry && time() - $this->model->getData('last_login_time') < 86400) {
  239. $this->setError('Please try again after 1 day');
  240. return false;
  241. }
  242. if (Config::get('buildadmin.admin_sso')) {
  243. Token::clear('admin', $this->model->id);
  244. Token::clear('admin-refresh', $this->model->id);
  245. }
  246. $this->setRefreshToken($this->refreshTokenKeepTime);
  247. $this->loginSuccessful();
  248. return true;
  249. }
  250. /**
  251. * 设置刷新Token
  252. * @param int $keepTime
  253. */
  254. public function setRefreshToken(int $keepTime = 0)
  255. {
  256. $this->refreshToken = Random::uuid();
  257. Token::set($this->refreshToken, 'admin-refresh', $this->model->id, $keepTime);
  258. }
  259. /**
  260. * 管理员登录成功
  261. * @return bool
  262. */
  263. public function loginSuccessful(): bool
  264. {
  265. if (!$this->model) {
  266. return false;
  267. }
  268. $this->model->startTrans();
  269. try {
  270. $this->model->login_failure = 0;
  271. $this->model->last_login_time = time();
  272. $this->model->last_login_ip = request()->ip();
  273. $this->model->save();
  274. $this->loginEd = true;
  275. if (!$this->token) {
  276. $this->token = Random::uuid();
  277. Token::set($this->token, 'admin', $this->model->id, $this->keepTime);
  278. }
  279. $this->model->commit();
  280. } catch (Throwable $e) {
  281. $this->model->rollback();
  282. $this->setError($e->getMessage());
  283. return false;
  284. }
  285. return true;
  286. }
  287. /**
  288. * 管理员登录失败
  289. * @return bool
  290. */
  291. public function loginFailed(): bool
  292. {
  293. if (!$this->model) {
  294. return false;
  295. }
  296. $this->model->startTrans();
  297. try {
  298. $this->model->login_failure++;
  299. $this->model->last_login_time = time();
  300. $this->model->last_login_ip = request()->ip();
  301. $this->model->save();
  302. $this->token = '';
  303. $this->loginEd = false;
  304. $this->model->commit();
  305. } catch (Throwable $e) {
  306. $this->model->rollback();
  307. $this->setError($e->getMessage());
  308. return false;
  309. }
  310. $this->model = null;
  311. return true;
  312. }
  313. /**
  314. * 退出登录
  315. * @return bool
  316. */
  317. public function logout(): bool
  318. {
  319. if (!$this->loginEd) {
  320. $this->setError('You are not logged in');
  321. return false;
  322. }
  323. $this->loginEd = false;
  324. Token::delete($this->token);
  325. $this->token = '';
  326. return true;
  327. }
  328. /**
  329. * 是否登录
  330. * @return bool
  331. */
  332. public function isLogin(): bool
  333. {
  334. return $this->loginEd;
  335. }
  336. /**
  337. * 获取管理员模型
  338. * @return Admin
  339. */
  340. public function getAdmin(): Admin
  341. {
  342. return $this->model;
  343. }
  344. /**
  345. * 获取管理员Token
  346. * @return string
  347. */
  348. public function getToken(): string
  349. {
  350. return $this->token;
  351. }
  352. /**
  353. * 获取管理员刷新Token
  354. * @return string
  355. */
  356. public function getRefreshToken(): string
  357. {
  358. return $this->refreshToken;
  359. }
  360. /**
  361. * 获取管理员信息 - 只输出允许输出的字段
  362. * @return array
  363. */
  364. public function getInfo(): array
  365. {
  366. if (!$this->model) {
  367. return [];
  368. }
  369. $info = $this->model->toArray();
  370. $info = array_intersect_key($info, array_flip($this->getAllowFields()));
  371. $info['token'] = $this->getToken();
  372. $info['refresh_token'] = $this->getRefreshToken();
  373. $info['group'] =Db::name('admin_group_access')->where('uid',$info['id'])->value('group_id');
  374. $info['wxbind']= Db::name('oauth_log')->where('user_id',$info['id'])->value('user_id');
  375. $info['gzbind']= Db::name('oauth_log')->where('user_id',$info['id'])->value('opid_status');
  376. return $info;
  377. }
  378. /**
  379. * 获取允许输出字段
  380. * @return array
  381. */
  382. public function getAllowFields(): array
  383. {
  384. return $this->allowFields;
  385. }
  386. /**
  387. * 设置允许输出字段
  388. * @param $fields
  389. * @return void
  390. */
  391. public function setAllowFields($fields): void
  392. {
  393. $this->allowFields = $fields;
  394. }
  395. /**
  396. * 设置Token有效期
  397. * @param int $keepTime
  398. * @return void
  399. */
  400. public function setKeepTime(int $keepTime = 0): void
  401. {
  402. $this->keepTime = $keepTime;
  403. }
  404. public function check(string $name, int $uid = 0, string $relation = 'or', string $mode = 'url'): bool
  405. {
  406. return parent::check($name, $uid ?: $this->id, $relation, $mode);
  407. }
  408. public function getGroups(int $uid = 0): array
  409. {
  410. return parent::getGroups($uid ?: $this->id);
  411. }
  412. public function getRuleList(int $uid = 0): array
  413. {
  414. return parent::getRuleList($uid ?: $this->id);
  415. }
  416. public function getRuleIds(int $uid = 0): array
  417. {
  418. return parent::getRuleIds($uid ?: $this->id);
  419. }
  420. public function getMenus(int $uid = 0): array
  421. {
  422. return parent::getMenus($uid ?: $this->id);
  423. }
  424. /**
  425. * 是否是超级管理员
  426. * @throws Throwable
  427. */
  428. public function isSuperAdmin(): bool
  429. {
  430. return in_array('*', $this->getRuleIds());
  431. }
  432. /**
  433. * 获取管理员所在分组的所有子级分组
  434. * @return array
  435. * @throws Throwable
  436. */
  437. public function getAdminChildGroups(): array
  438. {
  439. $groupIds = Db::name('admin_group_access')
  440. ->where('uid', $this->id)
  441. ->select();
  442. $children = [];
  443. foreach ($groupIds as $group) {
  444. $this->getGroupChildGroups($group['group_id'], $children);
  445. }
  446. return array_unique($children);
  447. }
  448. /**
  449. * 获取一个分组下的子分组
  450. * @param int $groupId 分组ID
  451. * @param array $children 存放子分组的变量
  452. * @return void
  453. * @throws Throwable
  454. */
  455. public function getGroupChildGroups(int $groupId, array &$children): void
  456. {
  457. $childrenTemp = AdminGroup::where('pid', $groupId)
  458. ->where('status', '1')
  459. ->select();
  460. foreach ($childrenTemp as $item) {
  461. $children[] = $item['id'];
  462. $this->getGroupChildGroups($item['id'], $children);
  463. }
  464. }
  465. /**
  466. * 获取分组内的管理员
  467. * @param array $groups
  468. * @return array 管理员数组
  469. */
  470. public function getGroupAdmins(array $groups): array
  471. {
  472. return Db::name('admin_group_access')
  473. ->where('group_id', 'in', $groups)
  474. ->column('uid');
  475. }
  476. /**
  477. * 获取拥有"所有权限"的分组
  478. * @param string $dataLimit 数据权限
  479. * @return array 分组数组
  480. * @throws Throwable
  481. */
  482. public function getAllAuthGroups(string $dataLimit): array
  483. {
  484. // 当前管理员拥有的权限
  485. $rules = $this->getRuleIds();
  486. $allAuthGroups = [];
  487. $groups = AdminGroup::where('status', '1')->select();
  488. foreach ($groups as $group) {
  489. if ($group['rules'] == '*') {
  490. continue;
  491. }
  492. $groupRules = explode(',', $group['rules']);
  493. // 及时break, array_diff 等没有 in_array 快
  494. $all = true;
  495. foreach ($groupRules as $groupRule) {
  496. if (!in_array($groupRule, $rules)) {
  497. $all = false;
  498. break;
  499. }
  500. }
  501. if ($all) {
  502. if ($dataLimit == 'allAuth' || ($dataLimit == 'allAuthAndOthers' && array_diff($rules, $groupRules))) {
  503. $allAuthGroups[] = $group['id'];
  504. }
  505. }
  506. }
  507. return $allAuthGroups;
  508. }
  509. /**
  510. * 设置错误消息
  511. * @param $error
  512. * @return Auth
  513. */
  514. public function setError($error): Auth
  515. {
  516. $this->error = $error;
  517. return $this;
  518. }
  519. /**
  520. * 获取错误消息
  521. * @return string
  522. */
  523. public function getError(): string
  524. {
  525. return $this->error ? __($this->error) : '';
  526. }
  527. }