0x01 實(shí)現(xiàn)步驟概述
技術(shù)棧:vue
+ node
+es6
+stylus
其中包含的庫與模塊:axios
酝静、crypto-js
、request
、router
- vue框架下前端頁面編寫
- 本地axios網(wǎng)絡(luò)請求,server端請求轉(zhuǎn)發(fā)
- 對網(wǎng)易云api加密進(jìn)行分析,偽造矾削,最后獲取信息
- 整體優(yōu)化,防止請求錯誤導(dǎo)致server異常退出
0x02 vue框架下前端頁面編寫
1.目錄結(jié)構(gòu)
目錄結(jié)構(gòu)是非常簡單的豁护,組件也非常少哼凯,一共有兩個組件,
- mHeader.vue組件是一個頭組件楚里,可以在這個組件放入logo或者一些標(biāo)識断部,我這里放入的是一個純色div
- Search.vue組件是搜索組件,在這個組件內(nèi)進(jìn)行歌曲班缎、歌手等搜索蝴光,也是我們著重編輯的部分
2.Router
為了以后添加更多的頁面組件,這里我們采取router路由的方式來規(guī)劃頁面及頁面間的跳轉(zhuǎn)
export default new Router({
routes: [
{
path: '/Search',
name: 'Search',
component: Search//引用Search頁面
},
{
path:'/',
redirect:'/Search' //重定向达址,跳轉(zhuǎn)到Search頁面
}
]
})
3.核心組件Search.vue
頁面很簡單蔑祟,主要是一個input框。現(xiàn)在我們希望在鍵入內(nèi)容時沉唠,在下方彈出提示內(nèi)容疆虚,像這樣:
所以我們?yōu)閕nput框綁定事件,方法有很多種满葛,vue下可以便捷的使用這種方式
<input @input="inputFun" placeholder="搜索感興趣的內(nèi)容" type="text">
其中inputFun就是我們綁定的事件径簿,每當(dāng)input內(nèi)容發(fā)生變化,就會執(zhí)行這個函數(shù)嘀韧。我們在methods中實(shí)現(xiàn)這個方法篇亭。
函數(shù)的具體實(shí)現(xiàn):
methods:{
inputFun(e){
this.searchConent = e.target.value //取出input內(nèi)容
console.log(this.searchConent)
if(this.searchConent.length < 1){ //判斷是否為空
this.info_flag = false //取消提示框的顯示
return
}
//執(zhí)行查詢
this.searchSubmit(this.searchConent, () => { //此時input不為空,執(zhí)行查詢函數(shù)锄贷,callback回調(diào)
if (this.searchConent.length < 1){
this.info_flag = false
return
}else{
this.info_flag = true
}
})
},
...//其他方法
}
searchSubmit函數(shù)執(zhí)行api查詢译蒂,以下是searchSubmit函數(shù)注意這里需要使用callback回調(diào),不然會出現(xiàn)問題谊却,不使用callback回掉會造成提示框出現(xiàn)過早蹂随,我們希望請求到數(shù)據(jù)以后再進(jìn)行顯示
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網(wǎng)絡(luò)請求,server端請求轉(zhuǎn)發(fā)
1. server中間轉(zhuǎn)發(fā)配置
vue在開發(fā)環(huán)境中調(diào)試時因惭,本質(zhì)就是啟動了node服務(wù)器,再用這個node服務(wù)器去啟動vue資源绩衷,那么我們的api請求代理轉(zhuǎn)發(fā)就可以寫在這個默認(rèn)的node服務(wù)器蹦魔,即在webpack.dev.conf.js中配置即可
這里我們需要在起始位置引入兩個模塊
//引入request模塊
const request = require('request')
//引入APIenc加密模塊
const enc = require('../API_ENC/enc.js')
其中request
模塊是一個http請求模塊激率,使用它可以輕松的完成GET、POST等請求勿决,我們用它來向網(wǎng)易云音樂獲取數(shù)據(jù)乒躺。
enc
模塊是我們自己編寫的加密模塊,網(wǎng)易云api請求對數(shù)據(jù)進(jìn)行了ASE低缩、RSA加密嘉冒,我們編寫模塊來重構(gòu)數(shù)據(jù)加密。
在devServer節(jié)點(diǎn)中建立before(app){}咆繁,在這里面寫http的get和post請求讳推,并通過api的形式傳回前端:
devServer: {
//此處設(shè)置代理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('數(shù)據(jù)空')
res.json({
error:0,
data:{}
})
}
//console.log(response.body)
})
}catch (e) {
}
}),
app.post('/api/post', (req, res) => {
res.json({
errno: 0,
data: 'helloPost'
})
})
},
//設(shè)置完畢
...其他代碼
}
其中的enc模塊就是我們抽象的網(wǎng)易云音樂加密模塊,在后面我們將詳細(xì)著重介紹這個模塊的編寫
另外需要注意的是請求中的headers需要進(jìn)行配置玩般,否則將不能獲取到數(shù)據(jù)银觅,設(shè)置user-agent和content-type是爬蟲和仿造請求的兩個重要手段,在以后很多情況都要用到坏为。
2. 前端axios請求api配置
當(dāng)然還需要在前端配置api究驴,其中我們使用了axios
模塊實(shí)現(xiàn),將axios的get請求封裝成search函數(shù)匀伏,并通過export進(jìn)行暴露洒忧。
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 對網(wǎng)易云api加密進(jìn)行分析熙侍,偽造,最后獲取信息
首先我們對網(wǎng)易云搜索提示的api進(jìn)行分析摧找,在輸入內(nèi)容前按下F12
并轉(zhuǎn)換到Network
保持網(wǎng)絡(luò)抓包開啟
我們可以看到數(shù)據(jù)很多核行,點(diǎn)開最后一個,可以在
preview
中看到數(shù)據(jù)內(nèi)容在
Headers
可以看到提交的請求數(shù)據(jù)請求結(jié)構(gòu)非常簡單蹬耘,但是
data
部分用到了加密芝雪,既然要偽造,那么就需要模擬加密過程综苔,這里推薦大家使用fiddler惩系,fiddler可以進(jìn)行全局搜索,方便我們逆向加密算法如筛,現(xiàn)在轉(zhuǎn)到fiddler堡牡,刷新網(wǎng)易云頁面,全局搜索encSecKey
包含關(guān)鍵字的包將會被高亮顯示
這個包是很可疑的杨刨,這是一個js庫晤柄,里面很可能就是加密算法,現(xiàn)在轉(zhuǎn)到瀏覽器
F12
中的Source
選項(xiàng)中打開這個js庫這個按鈕優(yōu)化代碼顯示
定位到包含
encSecKey
的位置很明顯妖胀,這個就是加密方法了芥颈,現(xiàn)在的主要問題是我們?nèi)绾稳ヌ崛∵@個方法到我們自己的模塊惠勒。其實(shí)很簡單,只要把加密過程走一遍分析他是什么類型的加密爬坑,這樣我們再重構(gòu)加密方法就易如反掌了纠屋。我們下一個斷點(diǎn)看看具體參數(shù)。
我們在
12861行
也就是函數(shù)頭部下一個斷點(diǎn)斷下來以后我們看到參數(shù)d就是被加密的原始數(shù)據(jù)盾计,而剩下3個參數(shù)都是固定值售担,不發(fā)生變化,可以視之為鹽署辉。
這三個按鈕分別是步過族铆,步入,和執(zhí)行到返回涨薪,善用調(diào)試骑素,可以讓問題變得簡單。
我們一步一步跟下去刚夺,發(fā)現(xiàn)執(zhí)行的順序是
所以只要模擬a,b,c這三個函數(shù)即可
function a(a) {//主要是取隨機(jī)生成鹽
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) {//數(shù)據(jù)進(jìn)行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) {//鹽進(jìn)行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方法則不可以直接復(fù)制了创橄,因?yàn)檫@里的c方法并不是正常的RSA加密,詳細(xì)的不同地方同學(xué)們可以深入調(diào)試莽红,可以重新自己寫c方法的加密妥畏,在這里不做過多介紹了,除了重寫加密方法外安吁,其實(shí)還有一種簡單的方法醉蚁,就是根據(jù)c函數(shù)的調(diào)用,找出對應(yīng)得方法而依次調(diào)用鬼店,我這里也是采取了這種方法网棍,直接從第12412行
到第12834行
全部粘貼到我們的模塊,經(jīng)過測試這種方式是完全正確的妇智。
所以整個enc模塊
看起來是這樣的:
const CryptoJS = require('crypto-js')
function RSAKeyPair(a, b, c) {
this.e = biFromHex(a),
this.d = biFromHex(b),
//......此處省略復(fù)制的代碼
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
到此滥玷,網(wǎng)易云api的加密分析就完成了,enc模塊的編寫也全部完成巍棱。
0x05 整體優(yōu)化惑畴,防止請求錯誤導(dǎo)致server異常退出
1. server部分優(yōu)化
在我測試的時候,會經(jīng)常發(fā)生server異常崩潰死掉的情況航徙,主要原因是請求的數(shù)據(jù)返回空如贷,而我們想要將之解析為json格式,那么將會拋出一個解析格式不正確的錯誤。所以需要加之判斷杠袱,判斷請求數(shù)據(jù)是否為空泻红,為空則返回空數(shù)據(jù),不為空則進(jìn)行解析霞掺。
if(response.body && response.body.length > 1){//數(shù)據(jù)不為空
console.log(response.body)
try{//嘗試進(jìn)行解析
res.json({
error:0,
data:JSON.parse(response.body)
})
}catch (e) {
console.log(e)
}
}else{
console.log('數(shù)據(jù)空')
res.json({
error:0,
data:{}
})
}
2. 前端優(yōu)化
前端我們創(chuàng)建了一個info_flag
標(biāo)志位來規(guī)定是否對提示框進(jìn)行顯示,然而info_flag
標(biāo)志位的true
false
切換時機(jī)變得尤為重要讹躯,為了更好的用戶體驗(yàn)菩彬,我們希望在生成數(shù)據(jù)以后進(jìn)行提示框的現(xiàn)實(shí),所以這里就用到了callback
回調(diào)潮梯,在請求到數(shù)據(jù)以后進(jìn)行回調(diào)骗灶,此時再進(jìn)行標(biāo)志位的切換。
searchSubmit(data, callback) {//獲取數(shù)據(jù)函數(shù)
search(data).then((res) => {
if(res.data.code == "200"){
this.searchInfoJson = res.data.result
callback()//獲取到數(shù)據(jù)以后再執(zhí)行callback函數(shù)來顯示info框
}else{
console.log('error:error')
}
})
}
至此秉馏,所有的前端和server中間代理就完成了
cnpm run dev //使用此語句來運(yùn)行吧耙旦!