实现方案:stringifyQuery 和 parseQuery

近期因为公司内部的安全检查,说我们现在的系统中参数是明文的,包括给后端请求的参数和前端页面跳转携带的参数,因为是公司内部使用的系统,在安全性方面的设计考虑确实不够充分

对于参数的加密和解密很好实现,直接采用常用的 AES 算法,前后端定义好通用的密钥和加解密方式就好,前端加解密这里主要使用到 crypto-js 这个工具包,再通过一个类简单封装一下加解密的算法即可

// src\utils\cipher.ts
import { encrypt, decrypt } from 'crypto-js/aes'
import { parse } from 'crypto-js/enc-utf8'
import pkcs7 from 'crypto-js/pad-pkcs7'
import ECB from 'crypto-js/mode-ecb'
import UTF8 from 'crypto-js/enc-utf8'// 注意 key 和 iv 至少都需要 16 位
const AES_KEY = '1111111111000000'
const AES_IV = '0000001111111111'export class AesEncryption {private keyprivate ivconstructor(key = AES_KEY, iv = AES_IV) {this.key = parse(key)this.iv = parse(iv)}get getOptions() {return {mode: ECB,padding: pkcs7,iv: this.iv,}}encryptByAES(text: string) {return encrypt(text, this.key, this.getOptions).toString()}decryptByAES(text: string) {return decrypt(text, this.key, this.getOptions).toString(UTF8)}
}

对于前端页面间跳转携带参数,我们项目使用的都是 vue-router 的 query 来携带参数,但是有那么多页面跳转的地方,不可能都手动添加加解密方法处理吧,工作量大不说,万一漏改一个就可能导致整个页面无法加载了,这锅可不能背

首先想到的方法是在路由守卫 beforeEach 中对参数进行加密,然后在 afterEach 守卫中对参数进行解密,但是这个想法在 beforeEach 中加密就无法实现。原因是 beforeEach(to, from, next) 的第三个参数 next 函数中,如果参数是路由对象,会导致跳转死循环

接下来经过几个小时百思不得其解(摸鱼)之后,最终在 API 参考 | Vue Router (vuejs.org) 找到这样两个 API:stringifyQueryparseQuery,官网的定义如下

stringifyQuery:对查询对象进行字符串化的自定义实现。不应该在前面加上 ?。应该正确编码查询键和值

parseQuery:用于解析查询的自定义实现。必须解码查询键和值

比如,官网建议如果想使用 qs 包来解析查询,可以这样配置

import qs from 'qs'createRouter({// 其他配置...parseQuery: qs.parse,stringifyQuery: qs.stringify,
})

现在最终的解决方案就很明确了,自定义两个参数加密、解密的方法,然后在 createRouter 中添加到 stringifyQueryparseQuery 这两个方法就可以了,下面是详细代码

// src/router/helper/query.js
import { isArray, isNull, isUndefined } from 'lodash-es'
import { AesEncryption } from '@/utils/cipher'
import type {LocationQuery,LocationQueryRaw,LocationQueryValue,
} from 'vue-router'const aes = new AesEncryption()/**** @description 解密:反序列化字符串参数*/
export function stringifyQuery(obj: LocationQueryRaw): string {if (!obj) return ''const result = Object.keys(obj).map((key) => {const value = obj[key]if (isUndefined(value)) return ''if (isNull(value)) return keyif (isArray(value)) {const resArray: string[] = []value.forEach((item) => {if (isUndefined(item)) returnif (isNull(item)) {resArray.push(key)} else {resArray.push(key + '=' + item)}})return resArray.join('&')}return `${key}=${value}`}).filter((x) => x.length > 0).join('&')return result ? `?${aes.encryptByAES(result)}` : ''
}/**** @description 解密:反序列化字符串参数*/
export function parseQuery(query: string): LocationQuery {const res: LocationQuery = {}query = query.trim().replace(/^(\?|#|&)/, '')if (!query) return resquery = aes.decryptByAES(query)query.split('&').forEach((param) => {const parts = param.replace(/\+/g, ' ').split('=')const key = parts.shift()const val = parts.length > 0 ? parts.join('=') : nullif (!isUndefined(key)) {if (isUndefined(res[key])) {res[key] = val} else if (isArray(res[key])) {;(res[key] as LocationQueryValue[]).push(val)} else {res[key] = [res[key] as LocationQueryValue, val]}}})return res
}// src/router/index.js
// 创建路由使用加解密方法
import { parseQuery, stringifyQuery } from './helper/query'export const router = createRouter({// 创建一个 hash 历史记录。history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),routes: basicRoutes,scrollBehavior: () => ({ left: 0, top: 0 }),stringifyQuery, // 序列化query参数parseQuery, // 反序列化query参数
})

加密的效果如下,我也在 github 上传了加密方式的 demo,可以直接下载体验一下

更进一步:相关实现原理

在实现完这两个功能之后,我突然想翻一下 Vue Router 的源码,看一下 stringifyQueryparseQuery 的实现原理,避免以后遇到类似的问题再抓瞎

打开 Vue Router@4的源码,整个项目是用 pnpm 管理 monorepo 的方式组织,通过 rollup.config.js 中定义的 input 入口可以知道,所有的方法都通过 packages/router/src/index.ts 导出

首先先看初始化路由实例的 createRouter 方法,这个方法主要做了这么几件事

1.通过 createRouterMatcher 方法,根据路由配置列表创建 matcher,返回 5 个操作 matcher 方法。matcher 可以理解为路由页面匹配器,包含路由所有信息和 crud 操作方法
2.定义三个路由守卫:beforeEach、beforeResolve、afterEach
3.声明当前路由 currentRoute,对 url 参数 paramas 进行编码处理
4.添加路由的各种操作方法,最后返回一个 router 对象

一个简化版本的 createRouter 方法如下所示,前文使用到的 stringifyQueryparseQuery 都是在这个方法中加载

export function createRouter(options: RouterOptions): Router {// 创建路由匹配器 matcherconst matcher = createRouterMatcher(options.routes, options)// ! 使用到的 stringifyQuery 和 parseQueryconst parseQuery = options.parseQuery || originalParseQueryconst stringifyQuery = options.stringifyQuery || originalStringifyQuery// ! 路由守卫定义const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()const afterGuards = useCallbacks<NavigationHookAfter>()// 声明当前路由const currentRoute = shallowRef<RouteLocationNormalizedLoaded>(START_LOCATION_NORMALIZED)let pendingLocation: RouteLocation = START_LOCATION_NORMALIZED// leave the scrollRestoration if no scrollBehavior is providedif (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {history.scrollRestoration = 'manual'}// url 参数进行编码处理const normalizeParams = applyToParams.bind(null,(paramValue) => '' + paramValue)const encodeParams = applyToParams.bind(null, encodeParam)const decodeParams: (params: RouteParams | undefined) => RouteParams =applyToParams.bind(null, decode) }

从创建路由实例来看, stringifyQueryparseQuery 两个参数如果没有自定义传入的情况下,会使用 vue-router 默认的解析函数

默认的 stringifyQuery 函数用于把参数由对象形式转换为字符串连接形式,主要流程

1.循环参数 query 对象
2.特殊处理参数为 null 的情况,参数值为 null 的情况会拼接在 url 链接中但是没有值,而参数值为 undefined 则会直接忽略
3.将对象转化为数组,并且对每个对象的值进行 encoded 处理
4.将数组拼接为字符串参数

// vue-router 默认的序列化 query 参数的函数
export function stringifyQuery(query: LocationQueryRaw): string {let search = ''for (let key in query) {const value = query[key]key = encodeQueryKey(key)// 处理参数为 null 的情况if (value == null) {if (value !== undefined) {search += (search.length ? '&' : '') + key}continue}// 将参数处理为数组,便于后续统一遍历处理const values: LocationQueryValueRaw[] = isArray(value)? value.map(v => v && encodeQueryValue(v)): [value && encodeQueryValue(value)]values.forEach(value => {// 跳过参数为 undefined 的情况,只拼接有值的参数if (value !== undefined) {search += (search.length ? '&' : '') + keyif (value != null) search += '=' + value}})}return search
}// 示例参数,如下参数会被转换为:name=wujieli&age=12&address
// query: {
// id: undefined,
// name: 'wujieli',
// age: 12,
// address: null,
// },

默认的 parseQuery 函数用来将字符串参数解析为对象,主要流程

1.排除空字符串和字符串前的 “?”
2.对字符串用 “&” 分割,遍历分割后的数组
3.根据 “=” 截取参数的 key 和 value,并对 key 和 value 做 decode 处理
4.处理 key 重复存在的情况,如果 key 对应 value 是数组,就把 value 添加进数组中,否则就覆盖前一个 value

 // vue-router 默认的序列化 query 参数的函数
export function parseQuery(search: string): LocationQuery {const query: LocationQuery = {}// 因为要对字符串进行 split('&') 操作,所以优先排除空字符串if (search === '' || search === '?') return query// 排除解析参数前的 ?const hasLeadingIM = search[0] === '?'const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&')for (let i = 0; i < searchParams.length; ++i) {// 根据 = 截取参数的 key 和 value,并做 decode 处理const searchParam = searchParams[i].replace(PLUS_RE, ' ')const eqPos = searchParam.indexOf('=')const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos))const value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1))// 处理 key 重复存在的情况if (key in query) {// an extra variable for ts typeslet currentValue = query[key]if (!isArray(currentValue)) {currentValue = query[key] = [currentValue]}// we force the modification;(currentValue as LocationQueryValue[]).push(value)} else {query[key] = value}}return query
}

stringifyQuery 这个方法用在创建 router 实例时提供的 resolve 方法中用来生成 url,parseQuery 方法主要用在 router.pushrouter.replace 等方法中解析 url 携带的参数

// stringifyQuery 方法的使用
function resolve( rawLocation: Readonly<RouteLocationRaw>,currentLocation?: RouteLocationNormalizedLoaded ): RouteLocation & { href: string } {// ...// 链接的完整 path,包括路由 path 和后面的完整参数const fullPath = stringifyURL(stringifyQuery,assign({}, rawLocation, {hash: encodeHash(hash),path: matchedRoute.path,}))
}// parseQuery 方法会封装在 locationAsObject 方法中使用
function locationAsObject( to: RouteLocationRaw | RouteLocationNormalized ): Exclude<RouteLocationRaw, string> | RouteLocationNormalized {return typeof to === 'string'? parseURL(parseQuery, to, currentRoute.value.path): assign({}, to)
}

以上就是 stringifyQueryparseQuery 两个方法的实现原理,可以看到源码中对于参数的加密解密考虑的处理是更多的,其实也可以把两个方法的源码拷贝出来,加上加密、解密的方法然后覆盖源码即可

vue 项目优雅的对 url 参数加密相关推荐

  1. asp.net中URL参数加密解密过程

    asp.net中URL参数加密解密过程 加密代码 public static string Encode(string str, string key){DESCryptoServiceProvide ...

  2. vue项目批量加载url文件并打包到zip下载

    vue项目批量加载url文件并打包到zip下载 项目里之前是遇到这样一个需求哈,需要根据选择的不同动态批量生成并下载图片, 而且这个图片不是一张一张生成下载,而是要等他选好条件之后, 把对应的图片动态 ...

  3. 前端url参数加密的解密

    1.当字符串过长时,需要用 encryptlong 来加密,否则会报参数过长的问题 2.当这个加密的值运用在url的参数加密中,如果url是另一个项目的话,会出现解密后值为空的现象 3.此时需要在加密 ...

  4. java url参数加密_针对url参数的加密解密算法(原创)

    基本思路是:前端对参数进行加密,并经过urlrewriter进行地址改写传入后台,后台再进行解密.如:对这样一个url--‍‍‍‍http://1.1.1.1:8080/a.do?param=1,加密 ...

  5. 【BUG】url 参数 AES 加密和解密问题

    原文地址: [BUG]url 参数 AES 加密和解密问题 欢迎访问我的博客: http://blog.duhbb.com/ 引言 bug 复盘, 让你少写 bug! 今天分析的是一个 url 参数加 ...

  6. vue项目中使用svg,通过img标签轻量解决方案,支持动态颜色更改

    前言 svg的优点不多说,怎么在vue项目优雅的使用svg?通过img似乎很优雅简单,但是你知道通过img引入svg怎么更改颜色吗?知道怎么动态切换颜色吗? 一.components下创建自定义公共s ...

  7. url 参数 后台 加密_一套拿来即用的后台管理系统,非常方便(附项目地址)

    前言 这套Base Admin是一套简单通用的后台管理系统,主要功能有:权限管理.菜单管理.用户管理,系统设置.实时日志,实时监控,API加密,以及登录用户修改密码.配置个性菜单等 技术栈 前端:la ...

  8. 用 cooking 搭建一个简单又优雅的 Vue 项目开发环境 (入门篇)

    本文适合 Vue 的初学者,以及对 webpack 不熟悉的同学阅读.前提是你要会用基本的命令行. Node 和 NPM,以及掌握 ES2015 的基础知识.本文都是在 macOS 环境下运行,要求使 ...

  9. vue 页面url参数_Vue下URL地址栏参数改变却不能刷新界面

    在完成毕业设计(基于Vue的信息资讯展示与管理平台)的过程中,处理如下图所示的 点击左侧栏目列表跳转到对应文章列表 的问题时,初次点击可以跳转到对应的页面,但是当第二次点击时,虽然地址栏的参数改变了, ...

最新文章

  1. 近期论文中的数据集整理0409
  2. 列举ospf的5种报文类型_这5种“专升本”你都知道吗?那个含金量更高呢?报考如何选择?...
  3. 重磅!全网最全13010本SCI2020最新影响因子下载!CA+四大神刊!预警期刊最新影响因子一览!
  4. [ 人机交互 ] 第三次作业 2015080360025秦嘉颍
  5. 简单工厂和策略模式结合
  6. JQuery data API实现代码分析
  7. C#托管代码调用C++非托管代码
  8. Postman系列之发送不同HTTP类型请求
  9. Serekh简介:新资产包,埋藏的回忆,第2卷
  10. 计算机右边键盘数字无效,巧妙解决电脑键盘右边的数字键失灵问题
  11. sublime配置go环境_如何为Sublime3配置Go语言开发环境
  12. 关于深度学习云服务器推荐
  13. 创立达摩院,马云是想当武林盟主吗?
  14. u盘插linux电脑不好使,u盘插电脑没反应怎么办的几种真正解决方式
  15. alios下载_AliOS Studio开源工具|AliOS Cloud App集成开发环境(AliOS Studio)下载 v1.2.1 官方Windows版 - 比克尔下载...
  16. 黏土基纳米复合水凝胶/PVA/PAA/BC复合水凝胶/聚乙烯醇PVA基复合水凝胶/壳聚糖/液晶(CS/LC)复合水凝胶/PVA/季铵盐壳聚糖复合水凝胶/有机-无机纳米复合水凝胶定制
  17. hive 修改分区备注_hive修改 表/分区语句
  18. 高效程序员的狂暴之路
  19. 关于dedecms织梦后台html编辑器不能复制word格式的处理方法/ kindEditor编辑器/百度(ueditor)编辑器的添加方法
  20. 亲测无限坐席在线客服系统源码,基于ThinkPHP的一款在线客服系统源码

热门文章

  1. 数据中心供配电系统负荷的计算方法(三)
  2. 记一次WebLogic10 PermGen space解决方法
  3. 如何开搓饵不掉钩_搓饵的制作步骤以及注意事项
  4. 数字孪生虚拟电厂负荷控制系统可视化
  5. 8.17 php-fpm的pool、php-fpm慢执行日志、open_basedir、php-fpm进程管理
  6. LeetCode174——地下城游戏
  7. 一款2018非常好玩的合击传奇版本
  8. 用DensePose,教照片里的人学跳舞,系群体鬼畜 | ECCV 2018
  9. idea查看meven历来_历来十大编程笑话
  10. 回调地狱和Promise