uni-app接口签名校验的实现方法

请求接口如果不做签名校验的话,数据很容易被抓包篡改,为了保证接口的安全我们需要对接口数据进行签名校验。
先贴一下完整的前端代码,后面再逐步讲解。
新建一个mixin.js文件,代码如下:

import md5 from "@/common/js/md5.js";
export default {
    data(){
        return {
           
        }
    },
    methods:{
        Get(url, data) {
            let sign = this.signature(data,this.$config.key);
            data.sign = sign;
            return new Promise((resolve, reject) => {
                uni.request({
                    url: this.$config.siteurl + url,
                    data: data,
                    method: 'GET',
                    success: (res) => {
                        resolve(res.data);
                    },
                    fail: (err) => {
                        reject(err);
                    }
                })
            })
       
        },
        Post(url, data) {
            let sign = this.signature(data,this.$config.key);
            data.sign = sign;
       
            return new Promise((resolve, reject) => {
                uni.request({
                    url: this.$config.siteurl + url,
                    data: data,
                    method: 'POST',
                    success: (res) => {
                        resolve(res.data);
                    },
                    fail: (err) => {
                        reject(err);
                    }
                })
            })
        },
        Upload(url,files,formData={}){
            return new Promise((resolve, reject)=>{
                if(typeof files == 'string'){
                    let imgarr = [];

                    formData.filePath = files;
                    let sign = this.signature(formData,this.$config.key);
                    formData.sign = sign
                   
                    uni.uploadFile({
                        url: this.$config.siteurl + url,
                        filePath: files,
                        name: 'file',
                        formData: formData,
                        success: (res) => {
                            imgarr.push(res.data)
                            resolve(imgarr);
                        }
                    });
                }
                if(Array.isArray(files) && files.length > 0){
                    let imgarr = [];
                    let Pros = [];
                    files.forEach((item,index)=>{
                        formData.filePath = item;
                        let sign = this.signature(formData,this.$config.key);
                        formData.sign = sign
                       
                        let Pro = new Promise((resolve1, reject1)=>{
                            uni.uploadFile({
                                url: this.$config.siteurl + url,
                                filePath: item,
                                name: 'file',
                                formData: formData,
                                success: (res) => {
                                    resolve1(res.data)
                                }
                            });
                        })
                        Pros.push(Pro)
                    })
                   
                    Promise.all(Pros).then((values) => {
                      resolve(values);
                    })
                }
            })
        },
        signature(data, key) {
            if (data.sign) {
                delete data.sign;
            }
            var n = null, d = {}, str = '', s = ''
            n = Object.keys(data).sort()
            for (var i in n) {
                if(this.isJSON(data[n[i]])) continue
                d[n[i]] = data[n[i]]
            }
            for (var k in d) {
                if (d[k] === '') continue
                if (str != '') str += '&'
                if(this.isContainChinese(d[k])){
                    str += k + '=' + encodeURIComponent(d[k])
                }else{
                    str += k + '=' + encodeURI(d[k])
                }
               
            }
            str += '&key=' + key
            s = md5.hex_md5(str).toUpperCase() // 这儿是进行MD5加密并转大写
            return s
        },
        //判断字符串是否是json
        isJSON(str) {
            if (typeof str == 'string') {
                try {
                    var obj=JSON.parse(str);
                    if(typeof obj == 'object' && obj ){
                        return true;
                    }else{
                        return false;
                    }

                } catch(e) {
                    return false;
                }
            }else{
                 return false;
            }
        },
        //判断字符串是否包含中文
        isContainChinese(val){
            if(/.*[\u4e00-\u9fa5]+.*$/.test(val)){
                return true;
            }
            return false;
        }
    }
}

然后在main.js中加在下面两行代码,即可把mixin.js中的方法变成全局方法

import mixin from './common/mixins/mixin.js'
Vue.mixin(mixin)

下面逐步来讲解一下实现步骤
1.首先封装一个生成签名的方法signature(),第一个参数data就是我们要传的接口数据,第二个参数key是我们自定义的一个字符串,用来参与签名的。
签名规则和微信支付接口签名规则一样,将请求数据按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值。

signature(data, key) {
    if (data.sign) {
        delete data.sign;
    }
    var n = null, d = {}, str = '', s = ''
    n = Object.keys(data).sort()
    for (var i in n) {
        if(this.isJSON(data[n[i]])) continue
        d[n[i]] = data[n[i]]
    }
    for (var k in d) {
        if (d[k] === '') continue
        if (str != '') str += '&'
        if(this.isContainChinese(d[k])){
            str += k + '=' + encodeURIComponent(d[k])
        }else{
            str += k + '=' + encodeURI(d[k])
        }
       
    }
    str += '&key=' + key
    s = md5.hex_md5(str).toUpperCase() // 这儿是进行MD5加密并转大写
    return s
},
//判断字符串是否是json
isJSON(str) {
    if (typeof str == 'string') {
        try {
            var obj=JSON.parse(str);
            if(typeof obj == 'object' && obj ){
                return true;
            }else{
                return false;
            }

        } catch(e) {
            return false;
        }
    }else{
         return false;
    }
},
//判断字符串是否包含中文
isContainChinese(val){
    if(/.*[\u4e00-\u9fa5]+.*$/.test(val)){
        return true;
    }
    return false;
}

2.在signature()方法中,我们用到了md5加密,所以要引入一下md5文件

import md5 from "@/common/js/md5.js";

md5.js的代码如下:

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */


/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */

var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */

function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */

function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */

function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */

function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */

function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */

function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */

function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */

function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */

function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */

function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */

function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}

module.exports = {
    hex_md5
}

3.然后封装三个请求接口的方法Get()、Post()、Upload()

Get(url, data) {
    let sign = this.signature(data,this.$config.key);
    data.sign = sign;
    return new Promise((resolve, reject) => {
        uni.request({
            url: this.$config.siteurl + url,
            data: data,
            method: 'GET',
            success: (res) => {
                resolve(res.data);
            },
            fail: (err) => {
                reject(err);
            }
        })
    })

},
Post(url, data) {
    let sign = this.signature(data,this.$config.key);
    data.sign = sign;

    return new Promise((resolve, reject) => {
        uni.request({
            url: this.$config.siteurl + url,
            data: data,
            method: 'POST',
            success: (res) => {
                resolve(res.data);
            },
            fail: (err) => {
                reject(err);
            }
        })
    })
},
Upload(url,files,formData={}){
    return new Promise((resolve, reject)=>{
        if(typeof files == 'string'){
            let imgarr = [];
            formData.filePath = files;
            let sign = this.signature(formData,this.$config.key);
            formData.sign = sign
           
            uni.uploadFile({
                url: this.$config.siteurl + url, //仅为示例,非真实的接口地址
                filePath: files,
                name: 'file',
                formData: formData,
                success: (res) => {
                    imgarr.push(res.data)
                    resolve(imgarr);
                }
            });
        }
        if(Array.isArray(files) && files.length > 0){
            let imgarr = [];
            let Pros = [];
            files.forEach((item,index)=>{
                formData.filePath = item;
                let sign = this.signature(formData,this.$config.key);
                formData.sign = sign
               
                let Pro = new Promise((resolve1, reject1)=>{
                    uni.uploadFile({
                        url: this.$config.siteurl + url, //仅为示例,非真实的接口地址
                        filePath: item,
                        name: 'file',
                        formData: formData,
                        success: (res) => {
                            resolve1(res.data)
                        }
                    });
                })
                Pros.push(Pro)
            })
           
            Promise.all(Pros).then((values) => {
              resolve(values);
            })
        }
    })
}

4.这三个方法中用到$config这个全局变量,我是把一些常用的全局变量挂载到了Vue.prototype上面
首先新建一个config.js,代码如下:

export default {
    name:"志博日记",
    siteurl: 'https://www.zhiboblog.com',
    key:'wCYR8Y6rQcj9sdb2'
}

然后在main.js中加入下面两行代码,就可以将config.js中的变量挂载到Vue.prototype上面

import $config from './common/js/config.js';//公共变量
Vue.prototype.$config = $config;

这样就可以通过我们封装好的请求数据的方法Get()、Post()、Upload()去请求接口了。
5.使用方法如下:

//发送get请求
this.Get('/api/api/test',{
    "username": "测试",
    "password":123456
}).then((res)=>{
    console.log(res)
}).catch((err)=>{
    console.log(err)
})
//发送post请求
this.Post('/api/api/test',{
    "username": "测试",
    "password":123456
}).then((res)=>{
    console.log(res)
}).catch((err)=>{
    console.log(err)
})
//上传文件
//tempFilePaths是文件路径列表,通过uni.chooseImage()方法选择图片后,会返回tempFilePaths
this.Upload('/api/api/upload',tempFilePaths).then((res)=>{
    console.log(res)
})

后端接收到数据后,要对传过来的签名进行校验。(后端我用的是thinkphp5.1)
在config目录下新建一个api.php文件,将参与签名的key写在里面,key需要与前端的key相同

<?php
return [
    //api密钥
    'key'=>'wCYR8Y6rQcj9sdb2',
];

然后在后端接口中加入下面几个方法,checkSign()是用来校验签名是否正确的,如果签名正确则可以拿到数据,否则提示非法请求

<?php
namespace app\api\controller;
use think\Controller;

class Api extends Controller
{
    protected function initialize()
    {
        $this->checkSign();
    }
   
    //接收数据的方法
    protected function requstData()
    {
        $data = input('param.');
        foreach ($data as $k=>$v){
            $data[$k] = urldecode($v);
        }
        if(isset($data['sign'])){
            unset($data['sign']);
        }
        return $data;
    }
    //检验api签名是否正确
    protected function checkSign()
    {
        $data = input('param.');
        if(!isset($data['sign'])){
            exit('非法请求');
        }
        $appsign = $data['sign'];
       
        $sign = $this->signature($data,config('api.key'));
        if($sign != $appsign){
            exit('非法请求');
        }
    }
   
    //生成签名
    protected function signature($data, $key) {
        if(isset($data['sign'])){
            unset($data['sign']);
        }
        ksort($data);
        $str = '';
        foreach ($data as $k => $v) {
            if ($v === '' || $this->isJson($v)) continue;
            if ($str !== '') $str .= '&';
            if ('UTF-8' === mb_detect_encoding($v)) {
                $v = rawurlencode($v);
            }
            $str .= "{$k}={$v}";
        }
        $str .= '&key=' . $key;
        $signature = md5($str);
        $signature = strtoupper($signature);
        return $signature;
    }
   
    //判断该字符串是否为Json格式
    protected function isJson($data='', $assoc = false)
    {
        if(is_string($data)){
            $data = json_decode($data, $assoc);
            if (($data && is_object($data)) || (is_array($data) && !empty($data))) {
                return true;
            }
            return false;
        }
        return false;
    }
    //上传文件接口
    public function upload()
    {
        $file = request()->file('file');
        $protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://": "http://";//协议头
        $siteurl = $protocol . $_SERVER['HTTP_HOST'];//站点网址
        if ($info = $file->validate(['ext' => 'jpg,jpeg,png,gif'])->move('upload')) {
            $imgurl = str_replace('\\', '/', '/upload/' . $info->getSaveName());
            return json_encode(['status' => 0, 'imgurl' => $siteurl.$imgurl]);
        } else {
            return $file->getError();
        }
    }
   
    //普通接口
    public function test()
    {
        $data = $this->requstData();//这是从接口获取到的数据
    }
}

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: