转载自:https://blog.csdn.net/weixin_30750335/article/details/96277139
Javascript网络编程常用的两种方式
短连接xmlhttprequest
长连接websocket
都需要考虑安全性
以下总结两个项目中所使用的相关技术
xmlhttprequest
xmlhttprequest.responseType
"text"(默认)
"json"(对象)
"arraybuffer"(二进制字节流)
服务端选择UTF-8编码返回JSON字符串
因此在不加密的情况下
可以设置responseType = "json"直接解析得到对象
websocket
websocket可以接收文本、ArrayBuffer、Blob类型的对象
取决于服务端推送的真实数据类型
wss.onmessage = function(msg) { if(msg.data instanceof ArrayBuffer) // ... if(msg.data instanceof Blob) // ... if(typeof msg.data == 'string') // ... }
websocket可以在向服务端传输数据时选择文本或字节流
文本十分简单就不提了
字节流传输可以自行编辑头部结构等等
注意若传输中文,需要先做encodeURI
var uintArray = new Uint8Array(content.length); for (var i = 0; i < content.length; i++) { uintArray[i] = content.charCodeAt(i); } this._ws.send(uintArray.buffer);
服务端客户端共用RC4算法对接口的返回结果做加密、解密
RC4算法加密、解密共用一套代码,首次执行加密,再次执行解密(异或)
服务端用密钥操作明文得到密文
客户端用密钥操作密文得到明文
需要注意的是,网上可以找到多种RC4算法的实现
包括各种语言的版本,实现细节往往不同
而服务端客户端往往使用不同语言
务必保证两端的RC4算法实现完全一致(可能需要自己手写)
随便抄两个版本的话往往对不上(比如网上搜到的PHP版本与JS版本就对不上)
PHP实现如下
/** * 相对应的 PHP 版本 RC4 * rc4加密 * @param string $data 数据 * @param string $password 密码 * @param bool $outBase64 是否返回base64 * @return string */ public function rc4Encrypt($data, $password, $outBase64 = FALSE) { $cipher = ''; $key[] = ''; $box[] = ''; $lengthData = strlen($data); $lengthPass = strlen($password); for ($i = 0; $i < 256; $i++) { $key[$i] = ord($password[$i % $lengthPass]); $box[$i] = $i; } for ($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $key[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0; $i < $lengthData; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $k = $box[(($box[$a] + $box[$j]) % 256)]; $cipher .= chr(ord($data[$i]) ^ $k); } return $outBase64 ? base64_encode($cipher) : $cipher; }
RC4编码往往会产生乱码
异或操作二进制文本的结果是随机的字节流
JS中默认使用UTF-16编码
而我们的服务端使用UTF-8编码
xmlhttprequest.responseType == 'text' 时
收到的密文貌似是被截断的(字节流自动转为文本时,由于编码不一致而截断)
因此需要已字节流的方式接收密文
xmlhttprequest.responseType = 'arraybuffer'
// 注意:不是xhr.responseText arrayBuffer = xhr.response // 对应UTF-8的字节流数组 byteArray = new Uint8Array(arrayBuffer) // 拼装密文 encodedString = String.fromCharCode.apply(null, byteArray) // RC4解码得到明文(会有中文字符乱码问题) decodedString = rc4Encrypt(encodedString , 'secret') // 处理中文乱码 escapedString = escape(decodedString) finalString = decodeURIComponent(escapedString) // 解析得到JSON对象 json = JSON.parse(finalString)
如果一定要用文本的方式传输数据的话
可能需要服务端在加密后做一遍encodeURI避免乱码
客户端收到密文后先做一遍decodeURI
服务端客户端共用一个密钥做md5签名运算
客户端发起请求时在header中加入参数的md5签名
服务端时候请求时,对参数做签名并与header中传入的签名做比较
以此排除非法请求
Javascript中设置header需要处理一些跨域问题
// 在header中添加签名 xhr.open("GET", url, true); xhr.setRequestHeader("CONTENT-TYPE", "application/x-www-form-urlencoded"); xhr.setRequestHeader('sign',strSign); xhr.send(); // 跨域问题服务端 // Access-Control-Allow-Headers: * // Access-Control-Allow-Origin: *
JS版本的MD5算法如下
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128< >>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e >5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t >5]|=(255&n.charCodeAt(t/8))< 16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r >>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); //# sourceMappingURL=md5.min.js.map //https://github.com/blueimp/JavaScript-MD5 // eg. md5('abc')
数据压缩可以一定程度上减小传输压力
加快传输速度
只需要服务端、客户端共同开启压缩和解压即可
常用的有gzip压缩,gzip对于中文也是支持的不会产生乱码
function uintToString(uintArray) { if (SOCKET.bGzip) { var encodedString = String.fromCharCode.apply(null, uintArray), decodedString = pako.inflate(encodedString, {to: "string"}); return decodedString; } else{ var encodedString = String.fromCharCode.apply(null, uintArray), decodedString = decodeURIComponent(escape(encodedString)); return decodedString; } }
下载地址
https://github.com/nodeca/pako/blob/master/dist/pako.min.js
参考文献
https://stackoverflow.com/questions/17191945/conversion-between-utf-8-arraybuffer-and-string