写在前面
路由组件之间传参,是实现不同页面之间获取数据的途径之一。Vue-router
可以通过对特定的 params
和 query
属性设置,URL 即可直接拼上相应的数据并实现页面直接数据传输,但如果有些数据比较敏感,或者不允许直接修改 URL 上的参数,来获得不被允许的内容信息时,URL 数据明文显示就无法得到保障,这个时候我们就需要对URL相应的参数进行加密。
URL 参数加密实现的两种方式
想要实现 URL 参数加密、解密,主要是不希望数据直接明文显示在 URL 上,同时也不允许修改数据而获得不被允许的信息。基于 Vue-cli
,可以通过两种方式(只是本人认为的两种,还可以有很多方式):一种是局部参数加密,在传输数据前,会先通过加密函数对需要传递参数数据进行加密,获取数据时再通过解密函数进行解密即可;一种是全局参数加密,再全局前置守卫时对参数继续加密传递,获取数据时,可直接获得无加密的数据。
下面会详细介绍第二种方式 -- 全局参数加密思路。
了解 Vue-router
的 HashHistory
模式
HashHistory
的路由替换是通过两种方式:一种是 push
,一种是 replace
。
Push
是将新访问的路由添加到浏览器访问历史栈顶,而 replace
是替换当前路由。
以下是 push
方式从设置路由改变到视图更新的流程:
$router.push() -> HashHistory.push() -> History.transitionTo() -> History.updateRoute() -> { app._route = route } -> vm.render()
解析:
-> $router.push() //路由切换,调用方法
-> HashHistory.push() //根据 hash 模式调用,设置 hash 并添加在浏览器历史记录(window.location.hash = xx)
-> History.transitionTo() // 监听更新,更新则调用 History.updateRoute()
-> History.updateRoute() // 更新路由
-> { app._route = route } //替换当前 app 路由
-> vm.render() //更新路由
实现全局 URL 参数加密思路
了解完 Vue-router
的 HashHistory
模式后,并参考 Vue-router 源码了解,我们知道:
-
路由切换都会调用
push
方法; -
在路由第一次进入加载时,是不会调用
push
方法的; -
$router
是VueRouter
的实例对象,包含所有路由信息,对象以及方法,而$route
是当前路由信息对象,可以获取到当前路由的name
、path
、query
、params
等参数值;
结合我们 URL 参数加密的需求,可以做出相应的解决方案:
- 基于
Vue-cli
的new Vue
实例对象进行全局修改; - 对
new Vue
实例对象中_router
(即$router
) 对象 (也就是Vue-router
的实例对象)的push
方法进行修改,对所有 URL 参数进行加密; - 通过
Vue-router
的全局前置守卫,处理路由第一次加载事件和不需要加密的 URL; - 通过
Object.defineProperty
对new Vue
实例对象的_routerRoot
(根vue实例)属性进行劫持,重写get
方法,对_route
(即$route
)的相应加密参数数据进行解密。以下为源码说明;
// new Vue 时或者创建新组件时,在 beforeCreate 钩子中调用
Vue.mixin({
beforeCreate() {
if (isDef(this.$options.router)) { // 组件是否存在$options.router,该对象只在根组件上有
this._routerRoot = this // 这里的this是根vue实例
this._router = this.$options.router // VueRouter实例
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else { // 组件实例才会进入,通过$parent一级级获取_routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed() {
registerInstance(this)
}
})
部分代码实现与解析
- 对
new Vue
实例对象(以下命名为“VO”
)中_router
(即$router
) 对象 (也就是Vue-router
的实例对象)的push
方法进行修改,对所有 URL 参数进行加密;通过Object.create
创建一个新的对象,使用VO
来提供新创建的对象的__proto__
,来实现对push
方法进行修改,对所有参数值进行加密,同时在末尾加上“JM”
加密标识
// VO 为 new Vue 实例对象
let NVO = Object.create(VO);
NVO._router.newPush = NVO._router.push;
NVO._router.push = function push (location, onComplete, onAbort) {
// 给全部 url 参数加密
// encryUrl 为加密函数,可以使用 window.btoa、window.atob 或者 使用 crypto-js
let params = encryUrl(location.params);
NVO._router.newPush.call(VO._router, Object.assign({}, location, { params }), onComplete, onAbort);
};
encryUrl
加密方法:
const CryptoJS = require('crypto-js'); //引用AES源码js
const key = CryptoJS.enc.Utf8.parse('1234123412ABCDEF'); //十六位十六进制数作为密钥
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412'); //十六位十六进制数作为密钥偏移量
//加密方法
function Encrypt(params) {
//获取入参对象的第一个 key
const key = Object.keys(params)[0];
if (!key) {
return params;
}
//获取入参对象的第一个 key
const key = Object.keys(params)[0];
let srcs = CryptoJS.enc.Utf8.parse(params[key]);
let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
//末尾添加的'JM'表示加密标识
return Object.assign({}, params, { [key]: `${encrypted.ciphertext.toString().toUpperCase()}JM` });
}
- 通过
Vue-router
的全局前置守卫,处理路由第一次加载事件和不需要加密的 URL;进入路由前,都会触发beforeEach
全局前置守卫方法,此时对不需要加密的路由参数数据进行解密,并重新加载;同时路由如果是第一次加载进入,直接判断参数值是否已经加密,如果是加密则直接next()
,反之则在参数值上加上特殊符号"+00"
,实现前端拦截, URL 参数值不允许手动修改;
NVO._router.beforeEach((to, from, next) => {
//获取入参对象的第一个 key
const key = Object.keys(to.params)[0];
if (key) {
//判断 url 参数是否需要加密
if (to.matched.some(record => record.meta.encryUrl)) { // url 参数需要加密
if (isEncry(to.params[key])) {
next();
} else { // url 参数没有加密
// url 参数没有加密,直接通过参数进入页面时,需加上特殊符号 '+00',实现前端拦截
next({
name: to.name,
params: encryUrl(Object.assign({}, to.params, { [key]: `${to.params[key]}+00` })),
query: to.query
});
}
} else { // url 参数不需要加密
if (isEncry(to.params[key])) { //url 参数已加密
// 由于在 push 方法时,都统一加密了,所以这里需要解密
const value = replaceEncryTag(to.params[key]);
next({
name: to.name,
params: {
[key]: decryptUrl(value)
},
query: to.query
});
} else { //url 参数未加密
// 直接访问
next();
}
}
} else {
// 直接访问
next();
}
});
- 通过
Object.defineProperty
对new Vue
实例对象的_routerRoot
(根vue实例)属性进行劫持,重写get
方法,对_route
(即$route
)的相应加密参数值,去掉“JM”
加密标识后,进行解密,并返回_routerRoot
对象即可;