Hey Sign
Experience is the best teacher.–实践出真知.
在今天的blog 开始之前,先加一个小插曲,最近在负责一个项目的支付模块,有个需求就是服务器主动和客户端进行连接会话,那么用什么技术呢?
经过小组内的讨论,最后决定用WebSocket.
那么什么是WebSocket?
WebSocket 是一种网络通信协议.RFC6455 定义了它的通信标准.
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议.
为什么需要WebSocket呢?
了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议.它采用了请求/响应模型.通信请求只能由客户端发起,服务端对请求做出应答处理.
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息.
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦.
大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询.轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开).
WebSocket无疑是此需求的一个解决方案.
WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端.WebSocket 只需要建立一次连接,
就可以一直保持连接状态.这相比于轮询方式的不停建立连接显然效率要大大提高.
其工作流程如下:
如何工作的呢?
Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接.由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响.
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接.任何实际的 WebSockets 服务器端实现都需要一个异步服务器.
说白了就是它可以保证服务端主动与客户端发送请求,保持连接状态,这也是它的最大的特点.
有了这些,我们就可以去方便的使用WebSocket实现功能了,至于如何实现,在此就不详细介绍了.如果大家想要详细了解的话,可以阅读下此博主,上面介绍的还是很全的.
但是又有一个问题出现了,就是在支付的时候或者数据传输的时候,如何知道数据没有被恶意篡改呢?
回到我们今天的主题:签名算法
首先说一下大致的思路,就是当传输过来时,我们可以让客户端进行加密运算,其算法和后台算法一致,便于后台进行运算校验,
前端对所有的数据进行加密后,带着签名,发送给服务器,此时服务器可以对所有的参数进行处理,得到后台根据数据处理得到的签名,然后与前端的签名进行比对,
如果不一致,就说明中间的数据被恶意篡改了,那么就可以对此次请求进行返回,如果一致,说明数据是安全的,那么就可以处理请求了.
当然前提是 前端与后台的秘钥是一致的 ,都会根据秘钥进行计算,然后得到签名.
爱码如下:
测试类:
// 模拟前台 对数据进行加密
Map<String,String[]> map = new HashMap<>();
map.put("username",new String[]{"lisi"});
// 当然在开发中 密码不能是明文传输,也需要进行加密 可以通过DES算法进行加密
map.put("password",new String[]{"123412"});
// 对map 进行加密
String sign1 = SignUtil.getSignStr(map);
// 此sign 是根据参数进行加密得到的
// 秘钥 YH348shdjsdf8 秘钥是前端和后台 一致的,用来进行验证
String signStr = SignUtil.getSign(sign1, "YH348shdjsdf8");
// 将sign 放到参数中 这个Sign 通过秘钥产生的
map.put("sign",new String[]{"681cb6a8a014cb51f25743e68aba9179"});
// 后台处理数据
// 首先得到 Map 对map进行处理 除了sign 不处理
String houTai = SignUtil.getSignStr(map);
// 通过数据库 得到秘钥 YH348shdjsdf8 然后进行计算得到 sign
String signHouTai = SignUtil.getSign(houTai, "YH348shdjsdf8");
// 得到map中的sign 然后与后台中得到 的 map进行比较 如果一致 说明没有被修改,如果不一致,说明数据被修改了
if(signHouTai.equals(map.get("sign")[0])){
System.out.println("安全");
}else {
System.out.println("数据被篡改了");
}
System.out.println(map.get("sign")[0]+" 前端计算Sign");
System.out.println(signHouTai+ "后台计算Sign");
数据被篡改了
681cb6a8a014cb51f25743e68aba9179 前端计算Sign
58b428b3ad1c9ff38d03995a56066fe5 后台计算Sign
如果对 username 加上一栏"wangwu"
map.put("username",new String[]{"lisi","wangwu"});
安全
681cb6a8a014cb51f25743e68aba9179 前端计算Sign
681cb6a8a014cb51f25743e68aba9179后台计算Sign
对password 进行处理 减去一个值
map.put("password",new String[]{"1234"});
数据被篡改了
681cb6a8a014cb51f25743e68aba9179 前端计算Sign
b75e43ec693a6c2921db9f04d7d7f6e7后台计算Sign
SignUtil工具如下:
/**
* 参数 就是Request 中的 参数 注意 签名 不能进行加密 运算会造成 死循环的
* @param params
* @return
*/
public static String getSignStr(Map<String, String[]> params) {
if (params == null || params.isEmpty()) {
return "";
}
//把所有参数,按照Key升序排列
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);//升序排列
StringBuilder builder = new StringBuilder();
try {
for (String key : keys) {
// 如果 是签名 的key 的话 continue;
if ("sign".equals(key)) {
continue;
}
// 得到 其 value 数组
String[] values = params.get(key);
if (values != null && values.length > 0) {
for (String value : values) {
builder.append("&");
builder.append(key).append("=");
builder.append(URLEncoder.encode(value, "utf-8"));
}
}
}
return builder.toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
/**
* 得到 签名 根据秘钥 和 参数
* @param str
* @param secret
* @return
*/
public static String getSign(String str, String secret) {
// 通过 MD5进行 加密计算
return DigestUtils.getMD5(str + secret);
}
如此便可以完成签名算法了,对数据进行校验了,当然前提是不能让别人知道你的秘钥,如果攻击者知道了你的秘钥,那么后果不堪设想了.