Skip to content

用户鉴权与路由守卫

登录跳转

有的时候用户不知道什么原因JWT失效的,需要重新登录,这时候登录完成后应该返回之前的页面。详细代码见链接

有两种方法解决。

typescript
// 两种写法
// 1. 跳转的时候拿到存在localStorage的returnTo用于跳转
const returnTo = localStorage.getItem('returnTo')
// 2. 拿去路由内的参数用于return_to跳转
// 只需要其他地方使用 router.push('/sign_in?return_to='+ encodeURIComponent(route.fullPath))
const returnTo = route.query.return_to?.toString()

router.push(returnTo ||  '/')

跳过广告

封装一个组件,用于将跳过这个状态存储下来。

typescript
import { defineComponent, PropType } from 'vue'
import { RouterLink } from 'vue-router'
export const SkipFeatures = defineComponent({
  setup: (props, context) => {
    const onClick = () => {
      localStorage.setItem('skipFeatures', 'yes')
    }
    return () => (
      <span onClick={onClick}>
        <RouterLink to="/start">跳过</RouterLink>
      </span>
    )
  }
})

由于在localStroage内存储了skipFeatures的内容,若是有skipFeatures,则一律跳过广告,使用路由守卫,在welcome路由中添加beforeEnter的钩子。

typescript
// ***** 省略导入
export const routes: RouteRecordRaw[] = [
  { path: '/', redirect: '/welcome' },
  {
    path: '/welcome',
    component: Welcome,
    beforeEnter: (to, from, next) => {
      localStorage.getItem('skipFeatures') === 'yes' ? next('/start') : next()
    },
    children: [
      { path:'', redirect: '/welcome/1' },
      { path: '1', name: 'Welcome1', components: { main: First ,footer: FirstActions } },
      { path: '2', name: 'Welcome2', components: { main: Second ,footer: SecondActions } },
      { path: '3', name: 'Welcome3', components: { main: Third ,footer: ThirdActions } },
      { path: '4', name: 'Welcome4', components: { main: Forth ,footer: ForthActions } }
    ]
  }
  // *** 省略内容
]

然后在welcome的其他页面中使用,详细代码内容见链接

登录检查

设置请求拦截器,若缓存中存在jwt则在请求头内带上。

typescript
http.instance.interceptors.request.use(config => {
  const jwt = localStorage.getItem('jwt')
  if (jwt) {
    // config.headers!的!是用来作类型推断的
    config.headers!.Authorization = `Bearer ${jwt}`
  }
  return config
})

缓存失效的时候,建议要等请求新内容jwt回来时,再去请求其他的。创建一个me.tsx用于请求/me的接口,返回它的Promise。

typescript
import { AxiosResponse } from "axios";
import { http } from "./Http"

export let mePromise: Promise<AxiosResponse<{
  resource: {
    id: number;
  };
}>> | undefined

export const refreshMe = () => {
  mePromise = http.get<{ resource: { id: number } }>('/me')
  return mePromise
}

export const fetchMe = refreshMe

初始化的时候请求一次fetchMe()返回结果,然后设置白名单,然后在全局的前置钩子router.beforeEach内对跳转进行处理,如果不是白名单内的路由则查看请求回来的mePromise的状态是否成功,若不成功则跳转至/sign_in?return_to=***,***为当前路由的路径,在登录后用于直接调转。

typescript
import { routes } from './config/routes'
import { createApp } from 'vue'
import { App } from './App'
import { history } from './shared/history'
import { createRouter } from 'vue-router'
import '@svgstore'
import { mePromise, fetchMe } from './shared/me'

const router = createRouter({ history, routes })

fetchMe()

const whiteList: Record<string, 'exact' | 'startsWith'> = {
  '/': 'exact',
  '/start': 'exact',
  '/welcome': 'startsWith',
  '/sign_in': 'startsWith' 
}

router.beforeEach((to, from) => {
  for(const key in whiteList) {
    const value = whiteList[key]
    if(value === 'exact' && to.path === key) {
      return true
    }
    if(value ==='startsWith' && to.path.startsWith(key)) {
      return true
    }
  }
  return mePromise!.then(
    () => true,
    () => '/sign_in?return_to=' + to.path
  )
})

const app = createApp(App)
app.use(router)
app.mount('#app')

其次在登录的时候还要在刷新一下mePromise的状态,详细代码见链接