vue 和 react 的登录态拦截
前后端分离开发,对于未登录状态的话,则需要后端返回未登录的提示,一般后端接口走的拦截器,如果没登录会重定向到 passport ,接口返回的是登录页面的html文本,需要让后端对拦截器做下修改(如继承拦截器,修改掉重定向的逻辑)。
前端对于登录态的拦截分为两部分:
1、路由跳转时的登录拦截;
2、接口调用时的路由拦截。
上述两步可以统一在 axios
的拦截器中做到。
下面是 axios
拦截器的处理:
axios.js
import axios from 'axios'
// 添加登录拦截的axios实例
let instance = axios.create({
baseURL: '/api'
})
const NO_LOGIN_CODE = 1000
instance.interceptors.response.use(response => {
if (response.data.errorCode === NO_LOGIN_CODE) {
location.href = `https://login.action?ReturnUrl=${encodeURIComponent(location.href)}`
return Promise.reject(new Error('no login'))
} else {
return response.data
}
}, error => {
return Promise.reject(error)
})
export default instance
在这里对于用封装了拦截的axios进行接口调用做了统一登录拦截,如果后端返回的Code码为未登录的Code码,则重定向到登录页面。
对于路由切换时的登录拦截处理,则叫后端给提供一个最轻量级的接口,不做任何逻辑处理,就只需要走后端登录拦截器之后判断用户是否登录了即可(如果有获取用户名的需要,则可以让这接口也返回用户名)。
router.js
import axios from './utils/axios'
...
router.beforeEach(async (to, from, next) => {
if (sessionStorage.getItem('isLogin')) {
next()
} else {
axios.get('/getUserInfo.action').then(res => {
sessionStorage.setItem('isLogin', true)
next()
})
}
})
因为 react-router
并没有路由守卫,我们可以基于类似 vue 的路由鉴权想法,我们稍稍改造一下 react-router-config
,利用 Route 的 render 函数,通过判断拦截条件来实现不同的组件的跳转。
import React from 'react'
import { Route, Redirect, Switch } from 'react-router-dom'
const renderRoutes = (routes, authed, authPath = '/login', extraProps = {}, switchProps = {}) => routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={(props) => {
if (!route.requiresAuth || authed || route.path === authPath) {
return <route.component {...props} {...extraProps} route={route} />
}
return <Redirect to={{ pathname: authPath, state: { from: props.location } }} />
}}
/>
))}
</Switch>
) : null
export default renderRoutes
修改后的源码增加了两个参数 authed
、 authPath
和一个属性 route.requiresAuth
router.js
const routes = [
{ path: '/',
exact: true,
component: Home,
requiresAuth: false,
},
{
path: '/login',
component: Login,
requiresAuth: false,
},
{
path: '/user',
component: User,
requiresAuth: true, //需要登陆后才能跳转的页面
},
{
path: '*',
component: NotFound,
requiresAuth: false,
}
]
app.js
import React from 'react'
import { Switch } from 'react-router-dom'
//import { renderRoutes } from 'react-router-config'
import renderRoutes from './utils/renderRoutes'
import routes from './router.js'
const authed = false // 是否登录授权,如果登陆之后可以利用 redux 修改该值或者使用 sessionStorage 也可
const authPath = '/login' // 默认未登录的时候返回的页面
const App = () => (
<main>
<Switch>
{renderRoutes(routes, authed, authPath)}
</Switch>
</main>
)
export default App
有时候修改 react-router-config
达不到我们所需的效果,可以设计全局组件来管理是否登陆
configLogin.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
class ConfigLogin extends Component {
static propTypes = {
children: PropTypes.object,
location: PropTypes.object,
isLogin: PropTypes.bool,
history: PropTypes.object
};
componentDidMount () {
if (!this.props.isLogin) {
setTimeout(() => {
this.props.history.push('/login')
}, 300)
}
if (this.props.isLogin && this.props.location.pathname === '/login') {
setTimeout(() => {
this.props.history.push('/')
}, 300)
}
}
componentDidUpdate () {
if (!this.props.isLogin) {
setTimeout(() => {
this.props.history.push('/login')
}, 300)
}
}
render () {
return this.props.children
}
}
export default withRouter(ConfigLogin)
然后修改 App.js
<Router>
<ConfigLogin isLogin={ isLogin ? true : false }>
<Switch>
<Route
exact
path="/"
render={ () => <Redirect to="/layout/dashboard" push /> }
/>
<Route path="/login" component={ Login } />
<Route path="/layout" component={ RootLayout } />
<Route component={ NotFound } />
</Switch>
</ConfigLogin>
</Router>
因为我们对 axios
做了统一拦截处理,所以在路由切换时我们只需要调用下判断登录的接口即可,如果未登录,则直接在axios拦截器中重定向到登录页了,不会走到下面的逻辑。
如果已登录,则设置一个 sessionStorage
,这样后续路由切换就不需要每次都调用一次该接口了,提高页面切换速度。
不过也有可能设置了 sessionStorage
,但这时候用户登录过期了,这种情况一般是在页面停留过长时间(比如页面停留了1天),导致页面登录过期,这时候在页面切换时因为不会重新请求判断登录的接口,页面路由直接切换过去,但这时候页面里面有其他接口请求,这时候通过axios拦截器统一拦截即可。
使用 sessionStorage
虽然在页面切换的时候会快一些,但是如果登陆失效会导致页面加载呈现之后跳转登陆页,而不是直接跳转。 上述这种登录过期和设置 sessionStorage
是用户体验的取舍了。个人偏向于设置 sessionStorage
的做法。
部分页面需要登录,部分页面不需要登录
这种情况则在路由中添加一个 meta
信息,用来判断当前页面是否需要登录。
import axios from './utils/axios'
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home'
import Mine from './views/Mine'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
name: 'Home',
component: Home
}, {
path: '/mine',
name: 'Mine',
component: Mine,
meta: {
requireAuth: true
}
}
]
})
router.beforeEach(async (to, from, next) => {
if (!to.meta.requireAuth || sessionStorage.getItem('isLogin')) {
next()
} else {
axios.get('/getUserInfo.action').then(res => {
sessionStorage.setItem('isLogin', true)
next()
})
}
})
export default router
可能会遇到一种情况,比如用来判断用户是否登录的接口,同时要用来获取用户名(如果获取不到则显示登录按钮),那这时候需要分应用场景来判断是否要做登录拦截,只需要创建 两个 axios 实例,一个用于需要做登录拦截的请求,一个用于不做登录拦截的请求。
参考:
React路由鉴权