背景
近期做了一個 spa的單獨專案中,有個需求就是希望根據登入人來看下,這個人是不是有許可權進入當前頁面。雖然服務端做了進行介面的許可權,但是每一個路由載入的時候,都要去請求這個介面太浪費了。
需要考慮的
-
登入授權,使用者沒有登入只能訪問登入頁面,如果處於登入狀態則跳轉到當前使用者的預設首頁
-
路由授權,當前登入使用者的角色,如果對一個 URL 沒有許可權訪問,則會呈現403
-
資料授權,當訪問一個沒有許可權的API,則跳轉到 403 頁面
-
操作授權,當頁面中某個按鈕或者區域沒有許可權訪問則在頁面中隱藏
期望
在路由層面攔截掉,當前這個人對應路由沒有許可權,面丟擲 403,且原元件也不會載入,當然也不會有任何請求
準備工作
react router4 、react-router-config、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
/* 和服務端約定的資料格式:[
{url: 'xxx', authuserId: [],authRoleGroup }
]
該路由對應的所有角色或者角色組
*/
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
這裡我在路由層已經把redux 的props 放到路由裡面去了。只要被改函式包過的元件都可以不僅可以拿到 路由資訊,還可以拿到 redux 的資訊 這樣我們的元件就不需要 connect, z這樣我們的普通元件就比較純粹了
同時簡單介紹一下我用的幾個 recompose 的 api:
-
compose:
從右往左執行函式組合(右側函式的輸出作為左側函式的輸入 -
onlyUpdateForKeys:
指定更新鍵值,否則禁止更新元件 -
branch:
理解為 if else 返回什麼元件 -
renderComponent:
獲取元件並返回該元件的高階元件 -
renderNothing:
返回 null
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>
複製程式碼
結語
所以綜上看出可以看出核心都在高階元件,後續我會貼出專案地址, 現在先把核心方法放出來,寫的比較倉促歡迎留言指正。