0x01 实现步骤概述
技术栈:vue
+ node
+es6
+stylus
其中包含的库与模块:axios
、crypto-js
、request
、router
- vue框架下前端页面编写
- 本地axios网络请求,server端请求转发
- 对网易云api加密进行分析,伪造,最后获取信息
- 整体优化,防止请求错误导致server异常退出
0x02 vue框架下前端页面编写
1.目录结构
目录结构是非常简单的,组件也非常少,一共有两个组件,
- mHeader.vue组件是一个头组件,可以在这个组件放入logo或者一些标识,我这里放入的是一个纯色div
- Search.vue组件是搜索组件,在这个组件内进行歌曲、歌手等搜索,也是我们着重编辑的部分
2.Router
为了以后添加更多的页面组件,这里我们采取router路由的方式来规划页面及页面间的跳转
export default new Router({
routes: [
{
path: '/Search',
name: 'Search',
component: Search//引用Search页面
},
{
path:'/',
redirect:'/Search' //重定向,跳转到Search页面
}
]
})
3.核心组件Search.vue
页面很简单,主要是一个input框。现在我们希望在键入内容时,在下方弹出提示内容,像这样:
所以我们为input框绑定事件,方法有很多种,vue下可以便捷的使用这种方式
其中inputFun就是我们绑定的事件,每当input内容发生变化,就会执行这个函数。我们在methods中实现这个方法。
函数的具体实现:
methods:{
inputFun(e){
this.searchConent = e.target.value //取出input内容
console.log(this.searchConent)
if(this.searchConent.length < 1){ //判断是否为空
this.info_flag = false //取消提示框的显示
return
}
//执行查询
this.searchSubmit(this.searchConent, () => { //此时input不为空,执行查询函数,callback回调
if (this.searchConent.length < 1){
this.info_flag = false
return
}else{
this.info_flag = true
}
})
},
...//其他方法
}
searchSubmit函数执行api查询,以下是searchSubmit函数注意这里需要使用callback回调,不然会出现问题,不使用callback回掉会造成提示框出现过早,我们希望请求到数据以后再进行显示
searchSubmit(data, callback) {
search(data).then((res) => {
if(res.data.code == "200"){
this.searchInfoJson = res.data.result
callback()
}else{
console.log('error:error')
}
})
}
以上search.vue组件就算是写完了
0x03 本地axios网络请求,server端请求转发
1. server中间转发配置
vue在开发环境中调试时,本质就是启动了node服务器,再用这个node服务器去启动vue资源,那么我们的api请求代理转发就可以写在这个默认的node服务器,即在webpack.dev.conf.js中配置即可
这里我们需要在起始位置引入两个模块
//引入request模块
const request = require('request')
//引入APIenc加密模块
const enc = require('../API_ENC/enc.js')
其中request
模块是一个http请求模块,使用它可以轻松的完成GET、POST等请求,我们用它来向网易云音乐获取数据。
enc
模块是我们自己编写的加密模块,网易云api请求对数据进行了ASE、RSA加密,我们编写模块来重构数据加密。
在devServer节点中建立before(app){},在这里面写http的get和post请求,并通过api的形式传回前端:
devServer: {
//此处设置代理API
before(app) {
app.get('/api/test', (req, res) => {
console.log(req.query.data)
console.log('{"s":"'+req.query.data+'","limit":"8","csrf_token":""}')
const h = enc.enc('{"s":"'+req.query.data+'","limit":"8","csrf_token":""}')
const _data = 'params='+h.encText+'&encSecKey='+h.encSecKey
//const _data='params=I9poLQX4QhYUqTlGJ0BuBqrBGfjgpwEOy91ZkftCJVKEh2fEs0EMzJOgYGDTmEyz4GAwdhdAeZ3L0oQU%2BCcmJEBODxiqBinxplaKGtUpfp8%3D&encSecKey=cc402fbec71e6483371fdfc6f7e14701f54b8d0b731617803436647fa1ca8db8e77236287d4be8b21336f04d527e10a7948b6da773d3a5de638b0005a194fc6c48fa6e5de32dcf891c388feec4c97ec4c6b3b6bd208c1389d6776d1cbc16425c9e15847bdb42257390030a5b2660ab6d1db81200d4458f9f6d9e6640b7393f16'
console.log(_data)
try{
request.post({
url:'https://music.163.com/weapi/search/suggest/web?csrf_token=',
form:_data,
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
}, (error, response) => {
if(response.body && response.body.length > 1){
console.log(response.body)
try{
res.json({
error:0,
data:JSON.parse(response.body)
})
}catch (e) {
console.log(e)
}
}else{
console.log('数据空')
res.json({
error:0,
data:{}
})
}
//console.log(response.body)
})
}catch (e) {
}
}),
app.post('/api/post', (req, res) => {
res.json({
errno: 0,
data: 'helloPost'
})
})
},
//设置完毕
...其他代码
}
其中的enc模块就是我们抽象的网易云音乐加密模块,在后面我们将详细着重介绍这个模块的编写
另外需要注意的是请求中的headers需要进行配置,否则将不能获取到数据,设置user-agent和content-type是爬虫和仿造请求的两个重要手段,在以后很多情况都要用到。
2. 前端axios请求api配置
当然还需要在前端配置api,其中我们使用了axios
模块实现,将axios的get请求封装成search函数,并通过export进行暴露。
import axios from 'axios'
export function search (data) {
const url = './api/test'
console.log(data)
return axios.get(url, {
params: {
data:data
}
}).then((res) => {
console.log(res.data)
return res.data
})
}
之后,我们便可以在任何组件中轻松的使用这个api,
在search.vue组件中引入
import {search} from '../../api/httpReq'
0x04 对网易云api加密进行分析,伪造,最后获取信息
首先我们对网易云搜索提示的api进行分析,在输入内容前按下F12
并转换到Network
保持网络抓包开启
我们可以看到数据很多,点开最后一个,可以在
preview
中看到数据内容
在
Headers
可以看到提交的请求数据
请求结构非常简单,但是
data
部分用到了加密,既然要伪造,那么就需要模拟加密过程,这里推荐大家使用fiddler,fiddler可以进行全局搜索,方便我们逆向加密算法,现在转到fiddler,刷新网易云页面,全局搜索encSecKey
包含关键字的包将会被高亮显示
这个包是很可疑的,这是一个js库,里面很可能就是加密算法,现在转到浏览器
F12
中的Source
选项中打开这个js库
这个按钮优化代码显示
定位到包含
encSecKey
的位置
很明显,这个就是加密方法了,现在的主要问题是我们如何去提取这个方法到我们自己的模块。其实很简单,只要把加密过程走一遍分析他是什么类型的加密,这样我们再重构加密方法就易如反掌了。我们下一个断点看看具体参数。
我们在
12861行
也就是函数头部下一个断点
断下来以后我们看到参数d就是被加密的原始数据,而剩下3个参数都是固定值,不发生变化,可以视之为盐。
这三个按钮分别是步过,步入,和执行到返回,善用调试,可以让问题变得简单。
我们一步一步跟下去,发现执行的顺序是
所以只要模拟a,b,c这三个函数即可
function a(a) {//主要是取随机生成盐
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {//数据进行AES加密
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function c(a, b, c) {//盐进行RSA加密
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) {
var h = {}
, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
还原b方法可以直接用,不过需要注意引用Crypto-js库,这是一个专门的AES加密解密库。
而还原c方法则不可以直接复制了,因为这里的c方法并不是正常的RSA加密,详细的不同地方同学们可以深入调试,可以重新自己写c方法的加密,在这里不做过多介绍了,除了重写加密方法外,其实还有一种简单的方法,就是根据c函数的调用,找出对应得方法而依次调用,我这里也是采取了这种方法,直接从第12412行
到第12834行
全部粘贴到我们的模块,经过测试这种方式是完全正确的。
所以整个enc模块
看起来是这样的:
const CryptoJS = require('crypto-js')
function RSAKeyPair(a, b, c) {
this.e = biFromHex(a),
this.d = biFromHex(b),
//......此处省略复制的代码
lowBitMasks = new Array(0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535);
console.log('LOADING--------------------------------------------------------------------------')
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b),
d = CryptoJS.enc.Utf8.parse("0102030405060708"),
e = CryptoJS.enc.Utf8.parse(a),
f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function enc(data){
var h = {}
var i = a(16)
h.encText = b(data, '0CoJUm6Qyw8W8jud')
h.encText = b(h.encText, i)
var e = '010001'
var f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
h.encSecKey = c(i, e, f);
//console .log('encText:' + h.encText + '\n' + 'encSecKey:' + h.encSecKey)
return h
}
/*
test:
APIenc('{"s":"周杰伦","limit":"8","csrf_token":""}')
*/
//console.log(enc('{"s":"周杰伦","limit":"8","csrf_token":""}'))
exports.enc = enc
到此,网易云api的加密分析就完成了,enc模块的编写也全部完成。
0x05 整体优化,防止请求错误导致server异常退出
1. server部分优化
在我测试的时候,会经常发生server异常崩溃死掉的情况,主要原因是请求的数据返回空,而我们想要将之解析为json格式,那么将会抛出一个解析格式不正确的错误。所以需要加之判断,判断请求数据是否为空,为空则返回空数据,不为空则进行解析。
if(response.body && response.body.length > 1){//数据不为空
console.log(response.body)
try{//尝试进行解析
res.json({
error:0,
data:JSON.parse(response.body)
})
}catch (e) {
console.log(e)
}
}else{
console.log('数据空')
res.json({
error:0,
data:{}
})
}
2. 前端优化
前端我们创建了一个info_flag
标志位来规定是否对提示框进行显示,然而info_flag
标志位的true
false
切换时机变得尤为重要,为了更好的用户体验,我们希望在生成数据以后进行提示框的现实,所以这里就用到了callback
回调,在请求到数据以后进行回调,此时再进行标志位的切换。
searchSubmit(data, callback) {//获取数据函数
search(data).then((res) => {
if(res.data.code == "200"){
this.searchInfoJson = res.data.result
callback()//获取到数据以后再执行callback函数来显示info框
}else{
console.log('error:error')
}
})
}
至此,所有的前端和server中间代理就完成了
cnpm run dev //使用此语句来运行吧!
0x06 效果演示
点击github给个星星吧