文章目录
- 一、准备工作和token
- 1、准备工作
- 2、介绍token用法
- 二、创建storage,store,request
- 1、src目录:
- 2、封装storage(可选)
- 3、创建store
- 4、创建request
- 三、配置代理,封装路由router、设置路由守卫,在main.js中引入router、store
- 一、配置代理
- 二、封装路由router,并设置路由守卫
- 三、在main.js中引入router、store
- 四、登录页面实际使用
适合小白 ❤
一、准备工作和token
1、准备工作
-
了解(session,cookie)token
Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
token 是在服务端产生的一串字符串,以作客户端进行请求的一个令牌。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌(除非设置了有效期)。 -
token 优点
Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
Token 可以是无状态的,可以在多个服务间共享
减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。 -
安装vuex
cnpm install vuex --save
2、介绍token用法
在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
————————————————
版权声明:本文为CSDN博主「sleepwalker_1992」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sleepwalker_1992/article/details/82974703
二、创建storage,store,request
1、src目录:
注:
- 封装storage是可选的,不封装就用原生的呗;创建store是必须的!
- token一定要存在storage缓存中,否则刷新一下,store会重新被加载,token就没了;
- 那存在store是不是多余了,这个也是为了数据统一管理吧,也是数据可视化,因为缓存中的数据代码中是看不见的~
2、封装storage(可选)
// 封装操作localstorage本地存储的方法 模块化var storage = {set(key, value) {localStorage.setItem(key, JSON.stringify(value));// localStorage.key = value;// localStorage[key] = value;},get(key) {return JSON.parse(localStorage.getItem(key));},getForIndex(index) {return localStorage.key(index);},getKeys(){let items = this.getAll();let keys = [];for (let index=0;index<items.length;index++){keys.push(items[index].key);}return keys;},getLength() {return localStorage.length;},getSupport() {return (typeof (Storage) !== "undefined") ? true : false;},remove(key) {localStorage.removeItem(key);},removeAll() {localStorage.clear();},getAll() {let len = localStorage.length; // 获取长度let arr = new Array(); // 定义数据集for (var i = 0; i < len; i++) {// 获取key 索引从0开始var getKey = localStorage.key(i);// 获取key对应的值var getVal = localStorage.getItem(getKey);// 放进数组arr[i] = {'key': getKey,'val': getVal,}}return arr;}
}export default storage;
3、创建store
import Vue from 'vue'
import Vuex from 'vuex'
import storage from '@/model/storage'Vue.use(Vuex);
// 用Vuex.Store对象用来记录token
const store = new Vuex.Store({state: {// 存储tokentoken:"",userName:"" // 可选},getters: {getToken(state){return state.token || storage.get("token") || "";}},mutations: {// 修改token,并将token存入localStoragesetToken(state,token) {state.token = token;storage.set('token', token);console.log('store、localstorage保存token成功!');},delToken(state) {state.token = "";storage.remove("token");},// 可选setUserInfo(state, userName) {state.userName = userName;}},actions: {// removeToken: (context) => {// context.commit('setToken')// }},
});export default store;
4、创建request
import axios from 'axios'
import store from '@/store'
import router from '@/router'// create an axios instance
const service = axios.create({// index.js设置了代理(解决跨域) baseURL: "/invoice", // url = base url + request urltimeout: 5000, // request timeout
})//添加请求拦截器,若token存在则在请求头中加token,不存在也继续请求
service.interceptors.request.use(config => {// 每次发送请求之前都检测vuex是否存有token,放在请求头发送给服务器// Authorization是根据后端自定义字段config.headers.Authorization = store.getters.getToken;return config;},error => {console.log("在request拦截器显示错误:", error.response)return Promise.reject(error);}
);//respone拦截器
service.interceptors.response.use(response => {// 在status正确的情况下,code不正确则返回对应的错误信息(后台自定义为200是正确,并且将错误信息写在message),正确则返回响应return response.data.code == 200 ? response : Promise.reject(response.data.message);},error => { // 在status不正确的情况下,判别status状态码给出对应响应if (error.response) {console.log("在respone拦截器显示错误:", error.response)switch (error.response.status) {case 401://可能是token过期,清除它store.commit("delToken");router.replace({ //跳转到登录页面path: '/login',// 将跳转的路由path作为参数,登录成功后跳转到该路由query: { redirect: router.currentRoute.fullPath }});}}return Promise.reject(error.response.data);}
);export default service
三、配置代理,封装路由router、设置路由守卫,在main.js中引入router、store
一、配置代理
二、封装路由router,并设置路由守卫
/* eslint-disable */
import Vue from 'vue'
import Router from 'vue-router'
import Main from '@/main/index'import store from '@/store'
import storage from '@/model/storage'Vue.use(Router)const routes = [{path: '/',name: 'Main',redirect: '/login',// 某些页面规定必须登录后才能查看 ,可以在router中配置meta,将需要登录的requireAuth设为true,meta: {requireAuth: true,}},{path: '/login',component: () => import('@/views/login'),},{path: '/invoice',redirect: '/invoice',component: Main,},]},{path: '*',component: Main}
]const router = new Router({ routes: routes })// 设置路由守卫,在进页面之前,判断有token,才进入页面,否则返回登录页面
if (storage.get("token")) {store.commit("setToken", storage.get("token"));
}
router.beforeEach((to, from, next) => {// 判断要去的路由有没有requiresAuth// to.matched.some(r => r.meta.requireAuth) or to.meta.requiresAuthif (to.matched.some(r => r.meta.requireAuth)) {if (store.state.token) {next(); //有token,进行request请求,后台还会验证token} else {next({path: "/login",// 将刚刚要去的路由path(却无权限)作为参数,方便登录成功后直接跳转到该路由,这要进一步在登陆页面判断query: { redirect: to.fullPath } });}} else {next(); //如果无需token,那么随它去吧}
});export default router
三、在main.js中引入router、store
import Vue from 'vue'
import App from './App'
import router from './router'import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'
import store from '@/store'// 兼容ie
// import 'babel-polyfill';Vue.use(ElementUI);
Vue.config.productionTip = falsenew Vue({el: '#app',router,store,components: { App },template: '<App/>'
})
四、登录页面实际使用
只给出表单提交示例函数和依赖js
import { postLogin } from "@/api/login";submitForm(formName) {this.$refs[formName].validate(valid => {if (valid) {let that = this;// console.log('username',this.loginForm.username)// 通过校验规则后进入校验用户名密码是否正确// 没有后台接口可以自定义一个函数来模拟,替换postLoginpostLogin(this.loginForm.username, this.loginForm.password).then(res => {console.log(res);that.$store.commit("setToken", res.data.token);that.$store.commit("setUserInfo", res.data.account);this.$notify({title: "登录成功",type: "success",showClose: false,duration: 1000});setTimeout(() => {// 此时要判断/login后面的参数redirect,若无参数,进入主页;this.$router.push("/index");// 若有参数则参数为未有权限的那个路由,跳转到那个路由// this.$router.push(***); -- 具体要自己在这实现}, 1000);}).catch(error => {// 错误分为 status-请求错误 和 code-账号密码错误this.$message.error(error);console.log(error);});} else {// 不符合前端校验this.$message.error('format error:'+error);console.log('format error:',error);return false;}});}
上面依赖的@/api/login:
import request from '@/utils/request.js' // 前面封装的request
// 在request中,设置了根路径/invoice,所以每一个请求都会自动在前面带上invoice;
// 由于在config中的index配置过了代理(监听)的路径也是/invoice,但凡遇到/invoice路径就会替换为target目标地址(后台接口根地址)
// 实现跨域,即不限于只能访问根路径为127.0.0.1下的地址
export function postLogin(account,password) {console.log(account,password)return request({url: '/login',method: 'post',params:{// 具体传参(键)要看后台要求account:account,password:password}})
}