目录
Cookie是由服务器端生成的,发送给User-Agent(一般是浏览器),(服务器告诉浏览器设置一下Cookie),浏览器会将cookie以key/value 的形式保存在某个目录下的文本文件内,下一次请求同一网站时就发送该Cookie服务器(前提是浏览器设置Cookie)
Cookie的特点
生命周期:
我们可以通过设置cookie的Expires的值来设置一条Cookie信息的失效时间 默认是当浏览器关闭的时候失效 我们可以利用new Date() 的 setTime和getTime来设置失效时间
满足同源策略:
不同源的情况下,Cookie一样是无法传递的。但是我们会发现我们登录了百度账号之后,再邓丽百度知道或百度文库,账号都是登录状态,但是这几个网页之间的主机名是不同的。
js-cookie
js-cookie是什么?
js-cookie是一个简单的,轻量级的处理cookies的js API,用来处理cookie相关的插件
js-cookie的使用方法
npm install --save js-cookie
import Cookies from 'js-cookie'
js-cookie的添加 获取 删除
添加cookie
- // 创建一个名称为name,对应值为value的cookie,由于没有设置失效时间,默认失效时间为该网站关闭时
- Cookies.set(name, value)
-
- // 创建一个有效时间为7天的cookie
- Cookies.set(name, value, { expires: 7 })
-
- // 创建一个带有路径的cookie
- Cookies.set(name, value, { path: '' })
-
- // 创建一个value为对象的cookie
- const obj = { name: 'ryan' }
- Cookies.set('user', obj)
获取cookie
- // 获取指定名称的cookie
- Cookies.get(name) // value
-
- // 获取value为对象的cookie
- const obj = { name: 'ryan' }
- Cookies.set('user', obj)
- JSON.parse(Cookies.get('user'))
-
- // 获取所有cookie
- Cookies.get()
删除cookie
- // 删除指定名称的cookie
- Cookies.remove(name) // value
-
- // 删除带有路径的cookie
- Cookies.set(name, value, { path: '' })
- Cookies.remove(name, { path: '' })
cooike操作的封装
- import Cookies from 'js-cookie'
-
- const TokenKey = 'Admin-Token'
-
- export function getToken() {
- return Cookies.get(TokenKey)
- }
-
- export function setToken(token) {
- return Cookies.set(TokenKey, token)
- }
-
- export function removeToken() {
- return Cookies.remove(TokenKey)
- }
登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。
tips
ps:为了保证安全性,后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。
click事件触发登录操作:
- this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
- this.$router.push({ path: '/' }); //登录成功之后重定向到首页
- }).catch(err => {
- this.$message.error(err); //登录失败提示错误
- });
action:
- LoginByUsername({ commit }, userInfo) {
- const username = userInfo.username.trim()
- return new Promise((resolve, reject) => {
- loginByUsername(username, userInfo.password).then(response => {
- const data = response.data
- Cookies.set('Token', response.data.token) //登录成功后将token存储在cookie之中
- commit('SET_TOKEN', data.token)
- resolve()
- }).catch(error => {
- reject(error)
- });
- });
- }
- import router from './router'
- import store from './store'
- import { Message } from 'element-ui'
- import NProgress from 'nprogress' // 进度条
- import 'nprogress/nprogress.css' // 进度条样式
- import { getToken } from '@/utils/auth' // 获取身份信息
- import getPageTitle from '@/utils/get-page-title'
-
- NProgress.configure({ showSpinner: false }) // NProgress Configuration
-
- const whiteList = ['/login', '/auth-redirect'] // 无重定向白名单
- // 路由前置守卫
- router.beforeEach(async (to, from, next) => {
- // 启动进度条
- NProgress.start()
-
- //设置页面标题
- document.title = getPageTitle(to.meta.title)
-
- //确定用户是否已登录
- const hasToken = getToken()
-
- if (hasToken) {
- if (to.path === '/login') {
- //如果已登录,请重定向到主页
- next({ path: '/' })
- NProgress.done()
- } else {
- // 确定用户是否已通过getInfo获得其权限角色
- const hasRoles = store.getters.roles && store.getters.roles.length > 0
- if (hasRoles) {
- next()
- } else {
- try {
- //获取用户信息
- const { role } = await store.dispatch('user/getInfo')
-
- //基于角色生成可访问的路线图
- const accessRoutes = await store.dispatch('permission/generateRoutes', role)
-
- //动态添加可访问路由
- router.addRoutes(accessRoutes)
-
- // 以确保addRoutes是完整的
- next({ ...to, replace: true })
- } catch (error) {
- //删除令牌并转到登录页面重新登录
- await store.dispatch('user/resetToken')
- Message.error(error || 'Has Error')
- next(`/login?redirect=${to.path}`)
- NProgress.done()
- }
- }
- }
- } else {
-
- /*它没有令牌*/
-
- if (whiteList.indexOf(to.path) !== -1) {
- //在免费登录白名单中,直接进入
- next()
- } else {
- //其他没有访问权限的页面将重定向到登录页面。
- next(`/login?redirect=${to.path}`)
- NProgress.done()
- }
- }
- })
-
- router.afterEach(() => {
- //完成进度条
- NProgress.done()
- })
用户信息
没有把用户信息存储到本地 ,页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。(vuex刷新会丢失数据 写到路由拦截器里 每次去判断有没有用户信息)
- // get user info
- getInfo({ commit, state }) {
- // 获取用户信息及权限并保存
- return new Promise((resolve, reject) => {
- getInfo(state.token).then(response => {
- const { data } = response
- console.log(response);
- if (!data) {
- reject('Verification failed, please Login again.')
- }
-
- const { role, name, avatar, introduction } = data
-
- // roles must be a non-empty array
- if (!role || role.length <= 0) {
- reject('getInfo: roles must be a non-null array!')
- }
-
- commit('SET_ROLES', data.role)
- commit('SET_NAME', name)
- commit('SET_AVATAR', avatar)
- commit('SET_INTRODUCTION', introduction)
- resolve(data)
- }).catch(error => {
- reject(error)
- })
- })
- },
tips
假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。
页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。
我们通过request拦截器在每个请求头里面塞入token,好让后端对请求进行权限验证。并创建一个respone拦截器,当服务端返回特殊的状态码,我们统一做处理,如没权限或者token失效等操作。或者来做一些统一的异常处理,一劳永逸。 而且因为我们的 api 是根据 env 环境变量动态切换的,如果以后线上出现了bug,我们只需配置一下 @/config/dev.env.js 再重启一下服务,就能在本地模拟线上的环境了
环境变量
设置webpack配置文件 运行时可以检测环境(webpack对外暴露了 process可以拿到 env为环境变量。设置的值必须VUE_APP开头)
- //开发
- # just a flag
- ENV = 'development'
-
- # base api
- VUE_APP_BASE_API = '/dev-api'
-
- //生产
- # just a flag
- ENV = 'production'
-
- # base api
- VUE_APP_BASE_API = '/prod-api'
-
- //测试
- NODE_ENV = production
-
- # just a flag
- ENV = 'staging'
-
- # base api
- VUE_APP_BASE_API = '/stage-api'
axios 封装
- import axios from 'axios'
- import { MessageBox, Message } from 'element-ui'
- import store from '@/store'
- import { getToken } from '@/utils/auth'
-
- // create an axios instance
- const service = axios.create({
- baseURL: process.env.VUE_APP_BASE_API,//基础路径+请求地址
- // withCredentials: true, // 跨域请求时发送Cookie
- timeout: 5000 // request timeout
- })
-
- // request interceptor
- service.interceptors.request.use(
- config => {
- // do something before request is sent
- if (store.getters.token) {
- // 设置token请求头
- config.headers['X-Token'] = getToken()
- }
- return config
- },
- error => {
- // do something with request error
- console.log(error) // for debug
- return Promise.reject(error)
- }
- )
-
-
- //响应拦截器
- service.interceptors.response.use(
- response => {
- const res = response.data
- // 如果自定义代码不是20000,则判断为错误。
- // if (res.code !== 20000) {
- // Message({
- // message: res.message || 'Error',
- // type: 'error',
- // duration: 5 * 1000
- // })
-
- // // 50008:非法令牌;50012:其他客户端登录;50014:令牌已过期;
- // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
-
- // //为了重新登录
- // MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
- // confirmButtonText: 'Re-Login',
- // cancelButtonText: 'Cancel',
- // type: 'warning'
- // }).then(() => {
- // // 删除token
- // store.dispatch('user/resetToken').then(() => {
- // location.reload()
- // })
- // })
- // }
- // return Promise.reject(new Error(res.message || 'Error'))
- // } else {
- return res
- // }
- },
- error => {
- console.log('err' + error) // for debug
- Message({
- message: error.message,
- type: 'error',
- duration: 5 * 1000
- })
- return Promise.reject(error)
- }
- )
-
- export default service
前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。
tips
ddRouter 是 router 实例的一个方法, 可以 动态添加更多的路由规则
这个 api ,可以让你的 router 配置,不用在初始化router实例的时候就写进去
可以在 vue 实例化 以后,动态的添加更多的路由
- // router.js
- import Vue from 'vue';
- import Router from 'vue-router';
-
- import Login from '../views/login/';
- const dashboard = resolve => require(['../views/dashboard/index'], resolve);
- //使用了vue-routerd的[Lazy Loading Routes
- ](https://router.vuejs.org/en/advanced/lazy-loading.html)
-
- //所有权限通用路由表
- //如首页和登录页和一些不用权限的公用页面
- export const constantRouterMap = [
- { path: '/login', component: Login },
- {
- path: '/',
- component: Layout,
- redirect: '/dashboard',
- name: '首页',
- children: [{ path: 'dashboard', component: dashboard }]
- },
- ]
-
- //实例化vue的时候只挂载constantRouter
- export default new Router({
- routes: constantRouterMap
- });
-
- //异步挂载的路由
- //动态需要根据权限加载的路由表
- export const asyncRouterMap = [
- {
- path: '/permission',
- component: Layout,
- name: '权限测试',
- meta: { role: ['admin','super_editor'] }, //页面需要的权限
- children: [
- {
- path: 'index',
- component: Permission,
- name: '权限测试页',
- meta: { role: ['admin','super_editor'] } //页面需要的权限
- }]
- },
- { path: '*', redirect: '/404', hidden: true }
- ];
通过meta标签来标示改页面能访问的权限有哪些。如meta: { role: ['admin','super_editor'] }表示该页面只有admin和超级编辑才能有资格进入。里有一个需要非常注意的地方就是 404 页面一定要最后加载
通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。
- // store/permission.js
- import { asyncRouterMap, constantRouterMap } from 'src/router';
-
- function hasPermission(roles, route) {
- if (route.meta && route.meta.role) {
- return roles.some(role => route.meta.role.indexOf(role) >= 0)
- } else {
- return true
- }
- }
-
- const permission = {
- state: {
- routers: constantRouterMap,
- addRouters: []
- },
- mutations: {
- SET_ROUTERS: (state, routers) => {
- state.addRouters = routers;
- state.routers = constantRouterMap.concat(routers);
- }
- },
- //动态路由
- actions: {
- GenerateRoutes({ commit }, data) {
- return new Promise(resolve => {
- let accessedRoutes
- console.log(role);
- if (role.includes('admin')) {
- var arr = []
- asyncRoutes.forEach((item) => {
- if (item.hasOwnProperty('meta')) {
- if (item.meta.hasOwnProperty('roles')) {
- if (item.meta.roles.includes('admin')) {
- arr.push(item)
- }
- }
- }
- })
- accessedRoutes = arr || []
-
- // accessedRoutes = asyncRoutes || []
- } else {
- accessedRoutes = filterAsyncRoutes(asyncRoutes, role)
- }
- commit('SET_ROUTES', accessedRoutes)
- resolve(accessedRoutes)
- })
- }
- }
- };
-
- export default permission;
转载借鉴 vue-element-admin
作者:花裤衩
链接:https://juejin.cn/post/6844903478880370701