⑴ 最近網上流行的XSS是什麼意思
最近網上流行的XSS是小學生的惡稱,罵小學生的。
一是指某些人的想法、思維方式、對事物的認知和思考能力如孩子般幼稚、單純、天真。
二是特指某類相對於同齡的人,在游戲競技或者社交網路中, 態度傲慢、技術水準較差、拒絕與隊友溝通、獨斷專行、忽視團隊合作、甚至喜歡惡語相向的網遊玩家。
三是指對沒有接觸過社會或社會經驗不足。
(1)xadmin源碼分析擴展閱讀:
1、小學生技術菜,愛罵人,玻璃心(說他一句就掛機送人頭,不管說什麼,比如:中路的你不要再送了,然後他就說「我就送」,接著就開始了。)小學生的心思就像星空,摸不著猜不透。
2、大噴子(網路中對喜歡肆意謾罵、地域黑、招黑、互黑等網友的一種廣泛性定義。),不分青紅皂白就開噴。
3、說話不經過大腦考慮,以自我為中心,可能是在家被寵慣了。
4、沒有接觸過社會大家庭或接觸社會經驗不足。比如:參加工作,你要是不讓新人上,永遠都是新人。這也是小學生。
⑵ 請教 關於 許可權判斷 admin
說到CMS,最需要有的東西就是許可權控制,特別是一些復雜的場景,多用戶,多角色,多部門,子父級查看等等。最近在開發一個線下銷售的東東,這個系統分為管理員端,省代端,客戶端,門店端,銷售端, 部門端,部門老大下面分子部門等等,惡心的需求。我們這個項目使用yii框架開發,yii在php屆還是比較流行的,雖然說laravel現在橫行,但是一些部門一些團隊還是採用了yii框架,比如我們。 我是剛接觸yii這個框架,開始的時候對這種面向組件的框架甚是別扭。當時打算自己寫許可權的,自己創建許可權表,關聯表等,但是學習使用yii開發文檔後,發現有個許可權控制RBAC,藉助於yii-admin可以實現完美的許可權,菜單的控制。這篇博客分兩部門,第一部分我會講述怎麼搭建許可權管理包括:安裝yii-admin,創建許可權表,使用許可權控制菜單和訪問許可權等基本的操作,這部分大致說一下,想要看更詳細的步驟可以參考這個比較詳細的講解:/yiisoft/yii2 yii2-admin:https://github.com/mdmsoft/yii2-admin 當然你可以使用composer來安裝,這樣最好不過,如果你安裝好了yii,你就可以切換到項目目錄下,直接執行下面的命令: php composer.phar require mdmsoft/yii2-admin "~2.0" php composer.phar update 然後配置中加入yii-admin的配置項,值的注意的是如果yii2-admin配置在common目錄下是全局生效,那麼你在執行命令控制台的時候就會報錯,所以應將許可權控製作用於web模塊,我們這個項目沒有使用高級模板,所以你可以直接把配置寫在config下面的web.php中,配置如下: 先定義別名: 'aliases' => [ '@mdm/admin' => '@vendor/mdmsoft/yii2-admin', ], 在moles中添加admin組件: 'admin' => [ 'class' => 'mdm\admin\Mole', 'layout' => '@app/views/layouts/main_nifty',//yii2-admin的導航菜單 ], 添加添加authManager配置項: 需要強調的是,yii中的authManager組件有PhpManager和DbManager兩種方式,這兩種方式是由區別的,PhpManager將許可權關系保存在文件里,DbManager方式,將許可權關系保存在資料庫。我們採用保存在資料庫中的方式。 'authManager' => [ 'class' => 'yii\rbac\DbManager', // or use 'yii\rbac\DbManager' ], 添加as access: 'as access' => [ 'class' => 'mdm\admin\components\AccessControl', 'allowActions' => [ // add or remove allowed actions to this list // 'admin/*', //'*', 'site/*', 'api/*', ] ], 需要說的是未知不要放錯了,如下圖所示: 2、配置資料庫許可權表 這一步不用自己去寫,命令行切換到yii2目錄,執行下面命令,創建rbac需要的表,但是資料庫需要自己創建名字是:yii2basic,如果要執行命令,就需要把你剛下配置好的配置文件在在console.php中也寫一份,如果執行不成功,可以吧生成數據表的腳本拿出來自己執行。 yii migrate --migrationPath=@yii/rbac/migrations yii migrate --migrationPath=@mdm/admin/migrations 如果執行成功會生成5張表,還需要一張user表,你可以自己添加 menu //菜單表 auth_rule //規則表 auth_item_child //角色對應的許可權,parent角色,child許可權名 auth_item //角色、許可權表,type=1表示角色,type=2表示許可權 auth_assignment //角色與用戶對應關系表 如果全部成功的話,再訪問index.php?r=admin 就可以了看到許可權的控制可視化頁面,如果出錯,你認真查看錯誤原因,基本上都是配置不對。配置好的話,訪問其他頁面就沒有許可權了,然後你可以修改as access中的allowActions,在開發api或者一些共用的模塊的時候很有用,因為這些頁面不需要進行許可權的控制。 許可權控制頁面如下圖: 3、進行菜單控制 要進行菜單控制,就需要用到剛才創建的那幾個表中的menu表,左側的導航按照我們的設計應該可以通過許可權進行控制,寫死的導航不能達到目的,可擴展行還不強,所以菜單控制必須要支持。 需要注意的是,如果你的後台框架中用到了自己的layout,你需要自己去指定,我們這個項目就是,有我們自己的layout,上面再添加admin組件的時候已經添加了: 'layout' => '@app/views/layouts/main_nifty', 然後我們操作菜單列表。添加菜單項,然後在打開layout文件,其實獲取菜單的邏輯已經寫好了,在MenuHelper中,添加命名空間mdm\admin\components\MenuHelper; 然後注銷原來的導航,添加下面的代碼,基本上就可以實現許可權-用戶-導航的控制了。 echo Nav::widget( [ "encodeLabels" => false, "options" => ["class" => "sidebar-menu"], "items" => MenuHelper::getAssignedMenu(Yii::$app->user->id), ] ); 好了說完了,最後看一下這個頁面: 二、yii-admin優化和重寫 在使用的過程中,yii-admin實現的導航許可權控制遠不能滿足我們的需求,並且,這種組件試的開發,每個操作是完全獨立的,比如,檢查許可權,取菜單,取用戶信息,每個操作都需要執行SQL來進行下面是正常的檢查許可權和得到菜單的sql執行過程。其實這個過程是極其費時的,當用戶量比較多,菜單比較大,許可權表中的數據非常多的時候是不能這樣乾的,使用我們自己的sql檢測工具可以看到,這個過程執行了20條之多的sql語句: 在圖中可以看出,許可權檢查涉及了14次的sql查詢,菜單涉及了5次sql查詢,如此多的sql 執行一旦上線事沒有什麼並發可言的。yii-admin這個組件提供了方便的許可權控制,菜單控制,但是性能上面我們不敢苟同。查看源碼你就知道,這個組件在我看來是一個解耦比較高的組件,每個成分之間可以單獨的使用,這就需要每個操作必須要有自己獨立的資料庫來源,說白了就需要每次都執行sql去取到想要的值,中間很少使用連表查詢這樣的sql,其實10條sql做的功能,在耦合上網情況下,一條sql就搞定了。 像我這種人是不能忍受這么多不相關的sql執行的,所以我就在根源上面修改了yii-admin的許可權檢查部分,修改的方法是我自己想的,不一定對,也不一定適合所有的場景,下面就寫出來與大家分享。 1、菜單的優化 我們通過查看菜單的生成過程大致會執行了5條以上的sql,這個還算可以,我沒有做sql上的優化,原因是我們的菜單是要對應不同的角色和子父級關系,在原來的基礎上我添加了一個type來區分是那種角色能看到這種菜單,一級哪種角色對應某一個菜單顯示的層級關系。這樣管理員,省代用戶,客戶都會呈現不同的菜單。即使配置相同的許可權,不同層級的用戶也會看到不同的菜單。 我們的優化是緩存菜單的生成數據,我們這個菜單是定製的,沒有採用一開始配置的Nav::widget來呈現,而是我們自己循環層級關系,這樣雖然麻煩,但是能很好的提取菜單中我們需要的沒一個邏輯,比如:麵包屑的自動生成,就可以每次提取菜單的label,再比如子頁面,不同控制器下得左導航的高亮,下面是代碼,php和html混寫了,以後會慢慢的提取。 <ul class="nav nav-list"> <?php $idx = ; $request_url = '/' . $mod_id . '/' . $con_id . '/' . $act_id . '/'; foreach ($menus_new['list'] as $label => $menu): ?> <?php if (empty($menu['label']) && empty($menu['url'][])) { continue; } ?> <?php if(!isset($menu['items'])):?> <li class="<?php if (isset($menu['openurl']) && strstr($menu['openurl'], $request_url)) { echo 'active'; $breadcrumb[] = $menu['label']; } ?>"> <a href="<?php echo $menu['url'][] ?>"> <i class="menu-icon fa fa-<?php echo $menu['icon'] ?>"></i> <span class="menu-text"> <?php echo $menu['label'] ?> </span> </a> <b class="arrow"></b> </li> <?php else:?> <li class="<?php if (isset($menu['openurl']) && strstr($menu['openurl'], $request_url)) { echo 'open'; $breadcrumb[] = $menu['label']; } ?>"> <a href="index.html"data-target="#multi-cols-<?php echo $idx ?>"class="dropdown-toggle"> <i class="menu-icon fa fa-<?php echo $menu['icon'] ?>"></i> <span class="menu-text"> <?php echo $menu['label'] ?> </span> <b class="arrow fa fa-angle-down"></b> </a> <b class="arrow"></b> <ul id="multi-cols-<?php echo $idx ?>" class="submenu"> <?php foreach ($menu['items'] as $label => $menu): ?> <?php if (empty($menu) !is_array($menu)) { continue; } if(!isset($menu['items'])):?> <li class="<?php if (isset($menu['openurl']) && strstr($menu['openurl'], $request_url)) { echo 'active'; $breadcrumb[] = $menu['label']; } ?>"> <a href="<?php echo $menu['url'][] ?>"> <i class="menu-icon fa fa-caret-right"></i> <?php echo $menu['label'] ?> </a> <b class="arrow"></b> </li> <?php else:?> <li class="<?php if (isset($menu['openurl']) && strstr($menu['openurl'], $request_url)) { echo 'open'; $breadcrumb[] = $menu['label']; } ?>"> <a href="#" class="dropdown-toggle"> <i class="menu-icon fa fa-caret-right"></i> <?php echo $menu['label'] ?> <b class="arrow fa fa-angle-down"></b> </a> <b class="arrow"></b> <ul class="submenu"> <?php foreach ($menu['items'] as $label => $url): ?> <?php if (empty($url) !is_array($url)) { continue; } ?> <li class="<?php if (isset($url['openurl']) && strstr($url['openurl'], $request_url)) { echo 'active'; $breadcrumb[] = $url['label']; } ?>"> <a href="<?php echo $url['url'][] ?>"> <i class="menu-icon fa fa-caret-right"></i> <?php echo $url['label'] ?> </a> <b class="arrow"></b> </li> <?php endforeach ?> </ul> </li> <?php endif?> <?php endforeach ?> </ul> </li> <?php endif?> <?php $idx++; ?> <?php endforeach ?> </ul> 這個導航是我自己改了好多版總結出適合我們自己的方案,其中breadcrumb是控制麵包屑的顯示,有時間我會抽離php。我介紹的是菜單優化,現在才完成了第一步,菜單的顯示,說到優化我是採用緩存菜單數據的策略,就是緩存上面那個$menus_new['list'],策略如下: 這個策略使用角色緩存數據,就是使用每個角色的許可權加上uid和環境配置MD5以後生成key,考慮到用戶比較多每個用戶都緩存的話開銷太大,並且用戶相同許可權的的比較多,特殊許可權的可以特殊對待,這樣省去了存儲好多重復的數據,環境配置是區分線上數據和測試數據,便於我們進行調試。 過期機制:更重要的是緩存的過期機制,緩存有了但是當菜單或者許可權發生變化的時候就要更新緩存,這里我們引入了版本的概念,能做到緩存變更的最小開銷。比如菜單變化,所有人導航都應該修改,這里我們在redis中加入一個導航版本的變數,每次讀入緩存的時候都會先判斷這個版本與緩存中自己存儲版本是否一致,如果一致證明導航沒有變化,如果不一致認為菜單有修改,導航已過期,需要重新得到緩存,這樣相同的角色,只要有一個人更新了導航,其他人下次再進來的時候就會訪問到最新的導航(統一角色)。這個全局的redis變數會在導航變更和許可權變更的時候自動加1,保證版本的變化,這樣如果有4類角色,幾萬人的用戶,實際的數據修改只發生的4次(實際會比這個多,比如同一個角色不同的許可權,那麼他對應的redis key 就不一樣,它需要自己去取緩存)。具體的代碼實現如下: $user_id = Yii::$app->user->id; $breadcrumb = []; $menus_new['list'] = MenuHelper::getAssignedMenu($user_id); $redis_key = MenuHelper::getMenuKeyByUserId($user_id); $redis_menu = Yii::$app->redis->get($redis_key); $redis_varsion = getVersion(); if (!empty($redis_menu)) { $menus_new = json_decode($redis_menu, true); $old_version = isset($menus_new['version']) ? $menus_new['version'] : ''; //判斷菜單的版本號,便於及時更新緩存 if (!isset($menus_new['list']) empty($old_version) intval($old_version) != $redis_varsion) { $menus_new = getMenu($user_id, $redis_varsion, $redis_key); $log = json_encode([ 'user_id' => $user_id, 'varsion' => $redis_varsion, 'redis_key' => $redis_key, 'value' => $menus_new ]); writeLog($log, 'update_menu'); } } else { $menus_new = getMenu($user_id, $redis_varsion, $redis_key); } function getMenu($user_id, $varsion, $redis_key) { $menus_new['list'] = MenuHelper::getAssignedMenu($user_id); $menus_new['version'] = $varsion; Yii::$app->redis->set($redis_key, json_encode($menus_new)); Yii::$app->redis->expire($redis_key, 300); return $menus_new; } //設置更新key便於時時更新redis function getVersion() { $version_key = Yii::$app->params['redis_key']['menu_prefix'] . md5(Yii::$app->params['redis_key']['menu_version'] . Yii::$app->db->dsn); $version_val = Yii::$app->redis->get($version_key); return empty($version_val) ? 1 : $version_val; } 生成key和更新key的邏輯如下: /** * get menu one user by the id * @param $user_id * @return key string */ public static function getMenuKeyByUserId($user_id) { if (empty($user_id)) { return false; } $list = (new \yii\db\Query())->select('**') ->from('**') ->where(['user_id' => $user_id]) ->all(); if (empty($list)) { return false; } $role_str = ''; foreach ($list as $key => $value) { $role_str .= $value['item_name']; } $redis_key = Yii::$app->params['key'] . md5($role_str . Yii::$app->db->dsn); return $redis_key; } /** * 修改菜單更新狀態,更新redis */ public static function UpdateMenuVersion() { $version_key = Yii::$app->params['key'] . md5(Yii::$app->params['key'] . Yii::$app->db->dsn); $version_val = Yii::$app->redis->get($version_key); if (empty($version_val)) { $version_val = '1'; } else { $version_val++; } $log = json_encode([ 'user_id' => Yii::$app->user->id, 'version_key' => $version_key, 'version_val' => $version_val ]); writeLog($log, 'update_menu_version'); Yii::$app->redis->set($version_key, $version_val); } 2、導航的高亮,圖標,是否顯示 默認的導航高亮是按照模塊,控制器,方法來進行直接匹配的,這樣一來有一種需求無法滿足,比如:A控制器下得頁面下載B控制器下面高亮,這種事無法實現的,所以要修改他們高亮機制。我們沒有再採用他的高亮邏輯,而是自己實現了一個新的邏輯。我首先把要高亮的頁面url加入到菜單的data裡面,data是一個json數據,如下所示: {"icon": "fa fa-home", "visible": true, "openurl":"/web/site/index/"} 這樣我們通過openurl就能知道哪個導航高亮,在頁面中直接判斷當前請求的url在不在這個openurl裡面就可以,但是這樣做有缺點,必須要有把高亮的頁面加入到要高亮的導航裡面,如果頁面太多這種方式不怎麼好,但是我沒有想到更好的方法去解決,如果哪位大神有好的方法可以在中寫出,非常感謝。 圖標和可見性的控制可以藉助於MenuHelper中getAssignedMenu的回調方法實現,你可以在調用該方法的時候傳入回調方法,我直接寫的匿名方法,添加在了該方法裡面,如下所示: $user_type = Yii::$app->user->identity->type; $customer_id = Yii::$app->user->identity->customer_id; $callback_func = function($menu) use ($user_type, $customer_id) { $data = json_decode($menu['data'], true); $items = $menu['children']; $return = [ 'label' => $menu['name'], 'url' => [$menu['route']], ]; $return['visible'] = isset($data['visible']) ? $data['visible'] : ''; //菜單隱藏的邏輯 if (empty($return['visible'])) { return false; } $return['icon'] = isset($data['icon']) ? $data['icon'] : ''; //控制菜單打開的邏輯 $return['openurl'] = isset($data['openurl']) ? $data['openurl'] : ''; $items && $return['items'] = $items; return $return; }; 3、重寫許可權檢測 剛才已經說了,yii-admin 的許可權檢測執行太費時間,執行SQL太多,所以我打算重寫他的許可權檢查的方法,通過讀源碼可以看到,他們檢查是通過user中的can方法調用的,然後通過mdm\admin\components\AccessControl中的beforeAction實現的,我們可以看一下: /** * @inheritdoc */ public function beforeAction($action) { $actionId = $action->getUniqueId(); $user = $this->getUser(); //預留系統檢查許可權的邏輯,一旦重寫檢查許可權失敗,調用系統檢查許可權的方法 if ($user->can('/' . $actionId)) { return true; } $obj = $action->controller; do { if ($user->can('/' . ltrim($obj->getUniqueId() . '/*', '/'))) { return true; } $obj = $obj->mole; } while ($obj !== null); $this->denyAccess($user); } 因為全許可權的檢查包含了子父級檢查,也就是說 /admin/menu/update的許可權是對/admin/menu/* 和/admin/* 和 /*都可見的,所以我們會看到$user->can的調用會使用do -while來進行,這樣就增加的檢查的復雜度,執行的sql就會批量的增加,你想啊,沒一個父級的檢查都是一次全新的函數調用,所以最惡心的也莫過於此了,感興趣的同學可以去看看他的這個過程,當你自己調用這個函數檢測的時候就會發現,執行的sql不是一般的多。 下面是我的重寫方法,一條SQL,兼容了許可權,角色,批量檢查和未登錄用戶的許可權檢查,具體實現如下: /** * 許可權判斷方法 (先不要使用該方法,用的系統方法,效率極低,等有時間重寫之後再用) * @param string/array $permission_name 許可權值(URL 或者 許可權名)/批量檢測可以傳入數組 * @param int $user 用戶id,不傳值會取當前的登陸用戶 * @return boolen * @author zhaoyafei */ public static function permissionCheck($permission_name, $user = 0) { //檢查是否登陸過 if (Yii::$app->user->isGuest) { Yii::$app->response->redirect('/site/login'); } if (empty($permission_name)) { return false; } if (empty($user)) { $user = Yii::$app->user->id; } //管理員許可權不能直接返回true,會存在管理員type = 1分到非管理員許可權的人員(有坑) //匿名方法,處理管理員返回值的情況 /*$setAdminSet = function($param) use ($permission_name) { $paramtmp = $permission_name; if (is_array($paramtmp)) { if (count($paramtmp) == 1) { return true; } $paramtmp = array_flip($paramtmp); foreach ($paramtmp as $key => &$value) { $value = true; } } else { $paramtmp = true; } return $paramtmp; };*/ //檢查是否是管理員, 管理員都有許可權 /*if (empty($user)) { $user = Yii::$app->user->id; $user_type = Yii::$app->user->identity->type; if ($user_type == TYPE_ADMIN) { return $setAdminSet($permission_name); } } else { $user_sql = "SELECT type FROM xm_user WHERE id = :id"; $user_info = Yii::$app->db->createCommand($user_sql)->bindValue(":id", $user)->queryOne(); if (empty($user_info)) { return false; } if ($user_info['type'] == TYPE_ADMIN) { return $setAdminSet($permission_name); } }*/ //根據用戶去取許可權 $permission_list = []; $sql = "SELECT xc.child, xc1.child as role_name FROM xm_auth_assignment xa INNER JOIN xm_auth_item_child xc ON xa.item_name = xc.parent LEFT JOIN xm_auth_item_child xc1 ON xc.child = xc1.parent WHERE xa.user_id = :user_id"; $permission = Yii::$app->db->createCommand($sql) ->bindValue(":user_id", $user) ->queryAll(); if (empty($permission)) { return false; } //組合許可權列表 foreach ($permission as $key => $value) { if (!empty($value['child']) && !in_array($value['child'], $permission_list)) { $permission_list[] = $value['child']; } if (!empty($value['role_name']) && !in_array($value['role_name'], $permission_list)) { $permission_list[] = $value['role_name']; } } //匿名方法,處理子url生成 $getUrlList = function($url) { if (!strstr($url, '/')) { return [$url]; } $url = '/' . trim($url, '/'); $params = explode('/', $url); $param_arr = []; $param_str = []; if (!empty($params) && is_array($params)) { foreach ($params as $key => $value) { if (!empty($value)) { $param_arr[] = $value; } } } if (!empty($param_arr)) { $tmp_str = ''; $param_str[] = $url; $count = count($param_arr); //生成子父級關系 for ($i = $count -1; $i >= 0; $i--) { $tmp_str = '/' . $param_arr[$i] . $tmp_str; $chold_url = str_replace($tmp_str, '/*', $url); if (!in_array($chold_url, $param_str)) { $param_str[] = $chold_url; } } } return $param_str; }; //拼接檢查數據,兼容單傳和傳輸組的情況 $check_list = []; if (is_array($permission_name)) { foreach ($permission_name as $key => $value) { $check_list[$value] = $getUrlList($value); } } else { $check_list[$permission_name] = $getUrlList($permission_name); } if (empty($check_list)) { return false; } //批量檢查是否有許可權 $ret = []; foreach ($check_list as $key => $value) { $ret[$key] = false; foreach ($value as $k => $v) { if (in_array($v, $permission_list)) { $ret[$key] = true; break; } } } //兼容一維數組 if (count($ret) == 1) { $ret = array_values($ret); return $ret[0]; } return $ret; } 需要說明的是,注釋掉的部分是管理員的許可權檢查,如果是管理員會自動返回所有的許可權,但是這種不太好,因為實際情況中會分多種管理員,這樣管理員不一定擁有所有的許可權,如果這樣不是超級管理員就不能使用,所以用的時候還是要慎重,最好統一使用許可權檢查。如果感覺那個SQL執行太慢可以添加緩存,緩存過期的時間和菜單過期類似,當用戶的許可權有變動的時候和菜單修改的時候跟新緩存。兩一種解決辦法是把這個方法協程單利,利用單利只是執行一次許可權的查詢,檢查的階段可以單獨寫成方法提供。
⑶ 開箱即用的React前端框架——ReactAdmin
ReactAdmin是一個Github上免費開源的前端框架(不是組件庫,也不是模板,它是一個框架),採用es6、React和Material Design構建基於Rest/GraphQl API的Web應用程序。在React上star數超過8k。
https://github.com/marmelab/react-admin
ReactAdmin不是個UI組件庫,它是一個前端框架,因此你基本上基本上只要按照官網的文檔進行一些配置等然後在其基礎上開發自己的應用程序即可,可謂開箱即用,意識就是都給你集成好了。
你可以直接使用以下命令進行安裝(這是安裝react-admin及所有的依賴)
下面我們看一下官網提供的一個最簡單的示例,你可以在它的主倉庫中獲取
我們進入到simple中,大致看一下代碼和目錄結構
我們安裝一些依賴然後啟動
成功後打開瀏覽器,這是使用react-admin最簡單的一個例子
一圖了解
由於ReactAdmin是一個非常復雜的框架,你可以參考提供的文檔,我這里就不詳細介紹了,感興趣的可以直接看文檔,文檔是英文的,所有的說明都在文檔中。
https://marmelab.com/react-admin/
ReactAdmin它是一個集合,它將一些前端開發所需要的東西都集成了進來,然後做好,我們直接使用即可,不僅僅適合個人學習,也適合通過它來構建企業級的應用。我們不僅僅是拿過來直接用,我們可看一看別人是怎麼實現這樣的一個框架的,從源碼去學習會更快的提升自己的水平,希望對你有所幫助!