用户鉴权与路由守卫
登录跳转
有的时候用户不知道什么原因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
的状态,详细代码见链接。