談談 react 路由許可權
背景
近期做了一個 spa的單獨專案中有個需求就是希望根據登入人來看下這個人是不是有許可權進入當前頁面。雖然服務端做了進行介面的許可權,但是每一個路由載入的時候都要去請求這個介面太浪費了。
需要考慮的
-
登入授權,使用者沒有登入只能訪問登入頁面,如果處於登入狀態則跳轉到當前使用者的預設首頁
-
路由授權,當前登入使用者的角色,如果對一個 URL 沒有許可權訪問,則會呈現403
-
資料授權,當訪問一個沒有許可權的API,則跳轉到 403 頁面
-
操作授權,當頁面中某個按鈕或者區域沒有許可權訪問則在頁面中隱藏
<font face="黑體">我是黑體字</font>
期望
在路由層面攔截掉,當前這個人對應路由沒有許可權,面丟擲 403,且原元件也不會載入,當然也不會有任何請求
準備工作
react router4 、react-router-confg、recompose、狀態資料管理庫(本文用的 rematch )、hoc(高階元件)
登入授權方案
router 早期版本有進入路由的鉤子函式可以實現這一點,但是 router4 去掉了採取了新的方式。舉個例子,就好比登入來說。貼一段虛擬碼
// app.js <Router> <Switch> { user.userId ? <AuthorizedLayout routes={routes} /> : <UnauthorizedLayout /> } </Switch> </Router> // AuthorizedLayout.js <Slider routes={this.state.routes}> <Switch> <Route path='/' exact render={() => ( <Redirect to='/Home /> )} /> {renderRoutes(this.state.routes)} </Switch> </Slider> // UnauthorizedLayout .js就是你的 login 元件
路由授權方案
利用高階元件內部判斷 if 返回什麼 else 返回什麼。我在下面貼下我怎麼實現的
routes.js
import { Icon } from 'assets' import { WithAuthRouter } from '@components' import Home from './home' // 這裡也可以用非同步載入看個人喜好 const routes = [ { path: '/Home', / exact: true, root: true, icon: Icon.COLLABORATION, title: '主頁', component: WithAuthRouter(Home) }
WithAuthRouter.js
import React from 'react' import {connect} from 'react-redux' import { compose, onlyUpdateForKeys, branch, renderComponent } from 'recompose' import Exception from 'ant-design-pro/lib/Exception' // 校驗路由許可權元件 const isNotAuth = props => { // route 本路由表,user 全域性登入資訊、menu 服務端返回的選單列表 const { route: { path = null }, user = {}, menu = [] } = props const { userId = null, roleMap = [] } = user if (userId && roleMap.length) { const result = menu.find(i => i.menuUrl === path) if (result) { return !result.userIdList.includes(userId) && !result.userIdList.filter(i => roleMap.includes(i)).length > 0 } return true } return true } const NotFound = () => ( <Exception type='403' actions /> ) const renderWhileIsNotAuth = branch( isNotAuth, renderComponent(NotFound) ) const mapState = state => ({ user: state.user, menu: state.menu, inited: state.inited }) const getUserAndMenu = compose( connect(mapState), onlyUpdateForKeys(['user', 'menu']) ) export default compose( getUserAndMenu, renderWhileIsNotAuth )
資料授權
我這裡用的 業務原因僅僅是判斷登入失效所以和服務端約定返回失效的code碼在請求方發裡統一處理掉我這裡用的的 axios 因為有個攔截器所以在攔截器裡做了
axios.interceptors.response.use(config => { const { data, status } = config if (status && status === 200) { // 圖片上傳介面返回的是result const { success, response, errorResponse, result } = data if (success) { return response || result } else { message.error(errorResponse.msg) if (errorResponse && errorResponse.code === 9100) { window.location.reload() } return null } } return config }, err => { console.log(err) message.error('發生了未知錯誤') Promise.reject(err) })
操作授權
也是利用高階元件
// 鑑權元件 import { branch, renderNothing } from 'recompose' const withAuth = branch( props => { const getAuth = () => { return something } return !getAuth() }, renderNothing ) export default withAuth // 呼叫 const AuthBtn = Auth(Button) <AuthBtn {...someProps} > xxx </AuthBtn>