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

修改后的源码增加了两个参数 authedauthPath 和一个属性 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路由鉴权

posted @ 2020-06-28 03:58  xlupc  阅读(968)  评论(0编辑  收藏  举报